【转】阮一峰 Webpack 教程
原文地址:https://blog.csdn.net/userkang/article/details/83504048
说明:跟着原文学习做的学习笔记(稍有改动,推荐打开原文看完整版)
写在开头
此Webpack教程是阮老师在Webpack1.x的版本上做的。现在Webpack的版本已经改动较大,建议有基础的同学,就直接上官网看最新的就好了。这个教程可以用来了解下 Webpack的前世今生。
仓库地址: https://github.com/userkang/webpack-demos-cn
开始
这个项目是一些简单的 Webpack 示例集合
这些示例特意用简单明了的方式编写,你将会发现跟着这些例子学习这个强大的工具并非难事。
如何使用
首先,全局安装 Webpack 和 webpack-dev-server
$ npm i -g webpack webpack-dev-server
然后,克隆这个仓库
$ git clone https://github.com/userkang/webpack-demos-cn.git
安装依赖
$ cd webpack-demos
$ npm install
现在,去项目 demo* 目录下开始源码体验之旅吧
$ cd demo01
$ npm run dev
如果上面的命令没有自动打开浏览器,你可能需要自己在浏览器访问http://127.0.0.1:8080
Demo01: Entry file
入口文件是 Webpack 进行读取构建 bundle.js 文件的一个文件
例如, main.js 就是一个入口文件.
// main.js document.write('<h1>Hello World</h1>')
//index.html <html> <body> <script type="text/javascript" src="bundle.js"></script> </body> </html>
Webpack 遵循 webpack.config.js 来构建 bundle.js.
//webpack.config.js module.exports = { entry: './main.js', output: { filename: 'bundle.js' } }
启动服务,访问 http://127.0.0.1:8080 .
$ cd demo01
$ npm run dev
Demo02: Multiple entry files
多个入口文件也是可以的。在多页面应用中,每个页面拥有不同的入口文件,用这个就非常管用了。
// main1.js document.write('<h1>Hello World</h1>') // main2.js document.write('<h2>Hello Webpack</h2>')
//index.html <html> <body> <script src="bundle1.js"></script> <script src="bundle2.js"></script> </body> </html>
//webpack.config.js module.exports = { entry: { bundle1: './main1.js', bundle2: './main2.js' }, output: { filename: '[name].js' } }
Demo03: Babel-loader (source)
Loaders 是一种预处理器,它可以在 Webpack 编译之前把你应用中的静态资源进行转换 。
举个例子, Babel-loader 可以在 Webpack 编译这些 JS 文件之前,先将 JSX/ES6 语法的文件转换成普通 ES5 语法的文件。Webpack 官网可以查看目前支持的 loaders。
main.jsx 是一个 JSX 文件.
//main.jsx const React = require('react') const ReactDOM = require('react-dom') ReactDOM.render(<h1>Hello, world!</h1>, document.querySelector('#wrapper'))
//index.html <html> <body> <div id="wrapper"></div> <script src="bundle.js"></script> </body> </html>
//webpack.config.js module.exports = { entry: './main.jsx', output: { filename: 'bundle.js' }, module: { rules: [{ test: /\.jsx?$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: {presets: ['es2015', 'react']} } }] } }
上边的代码段用到了 babel-loader,它需要 Babel 的预设插件 babel-preset-es2015 and babel-preset-react 来转义 ES6 和 React。
Demo04: CSS-loader
Webpack 允许在 JS 文件里包含 CSS,并通过 CSS-loader来预处理 CSS 文件。
//main.js require('./app.css')
//app.css body { background-color: blue; }
//index.html <html> <head> <script type="text/javascript" src="bundle.js"></script> </head> <body> <h1>Hello World</h1> </body> </html>
//webpack.config.js module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, module: { rules: [{ test: /\.css$/, use: ['style-loader', 'css-loader'] }] } }
注意,你必须用到两个loaders 去转换 CSS 文件。一个是 CSS-loader 来读取 CSS 文件,另一个是 Style-loader用来把 <style> 标签插入 HTML 页面。
接下来,让我们启动服务。
$ cd demo04
$ npm run dev
事实上,Webpack 会将一个内联样式表插入到 index.html。
<head> <script type="text/javascript" src="bundle.js"></script> <style type="text/css"> body { background-color: blue; } </style> </head>
Demo05: Image loader
Webpack 可以在 JS 文件中包含图片。
//main.js var img1 = document.createElement('img') img1.src = require('./small.png') document.body.appendChild(img1) var img2 = document.createElement('img') img2.src = require('./big.png') document.body.appendChild(img2)
//index.html <html> <body> <script type="text/javascript" src="bundle.js"></script> </body> </html>
//webpack.config.js module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, module: { rules: [ { test: /\.(png|jpg)$/, use: [ { loader: 'url-loader', options: { limit: 8192 } } ] } ] } }
url-loader将图片文件转成 img 标签。如果图片的大小小于 8192 字节,将会被转成 base64 的格式,否则还事会转成一条普通的链接。
启动服务就可以看到,small.png和big.png这两张图片转成了不同的两种格式。
<img src="data:image/png;base64,iVBOR...uQmCC">
<img src="4853ca667a2b8b8844eb2693ac1b2578.png">
Demo06: CSS Module (source)
css-loader?modules 的 CSS Module 功能可以给 JS 模块的 CSS 设置一个局部作用域。你可以用 :global(selector) (更多) 关掉它,使样式变成全局的。
//index.html <html> <body> <h1 class="h1">Hello World</h1> <h2 class="h2">Hello Webpack</h2> <div id="example"></div> <script src="./bundle.js"></script> </body> </html>
app.css /* local scope */ .h1 { color: red; } /* global scope */ :global(.h2) { color: blue; }
//main.jsx var React = require('react') var ReactDOM = require('react-dom') var style = require('./app.css') ReactDOM.render( <div> <h1 className={style.h1}>Hello World</h1> <h2 className="h2">Hello Webpack</h2> </div>, document.getElementById('example') )
//webpack.config.js module.exports = { entry: './main.jsx', output: { filename: 'bundle.js' }, module: { rules: [ { test: /\.js[x]?$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['es2015', 'react'] } } }, { test: /\.css$/, use: [ { loader: 'style-loader' }, { loader: 'css-loader', // 开启 CSS Module 功能 options: { modules: true } } ] } ] } }
启动服务。访问 http://127.0.0.1:8080, 你将看到 h1 是红色的,因为它的样式是局部作用域,h2 是蓝色的,因为它的作用域是全局的。
Demo07: UglifyJs Plugin
Webpack 用一套插件系统扩展了它的功能。比如,UglifyJs Plugin 就是其中的一个流行的插件,它可以压缩混淆输出的 js 代码。
//main.js var longVariableName = 'Hello' longVariableName += ' World' document.write('<h1>' + longVariableName + '</h1>')
//index.html <html> <body> <script src="bundle.js"></script> </body> </html>
//webpack.config.js var webpack = require('webpack') var UglifyJsPlugin = require('uglifyjs-webpack-plugin') module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, // 使用插件 plugins: [new UglifyJsPlugin()] }
启动服务,将会看到 main.js 被压缩成了下面的样式
var o = 'Hello' ;(o += ' World'), document.write('<h1>' + o + '</h1>')
Demo08: HTML Webpack Plugin and Open Browser Webpack Plugin
这个例子将讲讲怎么载入第三方的插件。
html-webpack-plugin 将会为你创建一个 index.html 文件。
open-browser-webpack-plugin 可以在 Webpack 编译完成后打开一个新窗口。
//main.js document.write('<h1>Hello World</h1>')
//webpack.config.js var HtmlwebpackPlugin = require('html-webpack-plugin') var OpenBrowserPlugin = require('open-browser-webpack-plugin') module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, plugins: [ new HtmlwebpackPlugin({ title: 'Webpack-demos', filename: 'index.html' }), new OpenBrowserPlugin({ url: 'http://localhost:8080' }) ] }
启动服务。现在你不仅不需要手动的写 index.html 文件,而且也不用手动的打开浏览器了。Webpack 都帮你完成了。
Demo09: Environment flags
你可以用环境变量来区分开发环境。
//main.js document.write('<h1>Hello World</h1>') if (__DEV__) { document.write(new Date()) }
//index.html <html> <body> <script src="bundle.js"></script> </body> </html>
//webpack.config.js var webpack = require('webpack')
// DefinePlugin 允许创建一个在编译时可以配置的全局常量 var devFlagPlugin = new webpack.DefinePlugin({ __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false')) }) module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, plugins: [devFlagPlugin] }
现在打开 demo09/package.json,你需要在 scripts 字段下添加如下的代码,向 webpack 传递环境变量。
// package.json { // ... "scripts": { "dev": "cross-env DEBUG=true webpack-dev-server --open", }, // ... }
启动服务。
Demo10: Code splitting
对于一个大型 app,把所有代码塞到一个文件可能不是很好维护。Webpack 可以把一个庞大的 JS 文件拆分成几块。尤其,有些代码只有用的时候才引入可以按需加载。Webpack 用 require.ensure 来定义一个代码点。
// main.js require.ensure(['./a'], function(require) { var content = require('./a') document.open() document.write('<h1>' + content + '</h1>') document.close() })
require.ensure 告诉 Webpack ./a.js 应该从 bundle.js 中拆分出来,单独生成一个文件。
// a.js module.exports = 'Hello World'
现在 Webpack 来负责依赖、输出和运行时需要的一些东西。你不必在投入过多的精力在 index.html 和 webpack.config.js 上了。
//index.html <html> <body> <script src="bundle.js"></script> </body> </html> //webpack.config.js module.exports = { entry: './main.js', output: { filename: 'bundle.js' } }
启动服务。表面上,你不会感到什么变化。然而,Webpack 已经把 main.js 和 a.js 构建成两个不同的文件(bundle.js and 0.bundle.js),并且按依赖关系依次引入。
Demo11: Code splitting with bundle-loader
另一种进行代码分离的方法是用 bundle-loader。
// main.js // 现在 a.js 被引入,它将会被打包到另一个文件 var load = require('bundle-loader!./a.js') // 为了等待 a.js 中的模块变得可用 // 你可能需要使用 async wait load(function(file) { document.open() document.write('<h1>' + file + '</h1>') document.close() })
require('bundle-loader!./a.js') 告诉 Webpack 从另一个包里加载 a.js。
现在 Webpack 将会构建 main.js 到 bundle.js 中, a.js 到 0.bundle.js 中。
Demo12: Common chunk
当多个 Js 文件有共同的依赖,我们可以 CommonsChunkPlugin 把公共的部分提取出来生成一个文件。这对浏览器缓存和节省带宽是非常有用的。
// main1.jsx var React = require('react') var ReactDOM = require('react-dom') ReactDOM.render(<h1>Hello World</h1>, document.getElementById('a')) // main2.jsx var React = require('react') var ReactDOM = require('react-dom') ReactDOM.render(<h2>Hello Webpack</h2>, document.getElementById('b'))
index.html <html> <body> <div id="a"></div> <div id="b"></div> <script src="commons.js"></script> <script src="bundle1.js"></script> <script src="bundle2.js"></script> </body> </html>
上面的 commons.js 就是 main1.jsx 和 main2.jsx 的公共部分。也就是包含了 react 和 react-dom。
webpack.config.js var webpack = require('webpack') module.exports = { entry: { bundle1: './main1.jsx', bundle2: './main2.jsx' }, output: { filename: '[name].js' }, module: { rules: [ { test: /\.js[x]?$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['es2015', 'react'] } } } ] }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'commons', // (the commons chunk name) filename: 'commons.js' // (the filename of the commons chunk) }) ] }
Demo12: Common chunk
当多个 Js 文件有共同的依赖,我们可以 CommonsChunkPlugin 把公共的部分提取出来生成一个文件。这对浏览器缓存和节省带宽是非常有用的。
// main1.jsx var React = require('react') var ReactDOM = require('react-dom') ReactDOM.render(<h1>Hello World</h1>, document.getElementById('a')) // main2.jsx var React = require('react') var ReactDOM = require('react-dom') ReactDOM.render(<h2>Hello Webpack</h2>, document.getElementById('b'))
//index.html <html> <body> <div id="a"></div> <div id="b"></div> <script src="commons.js"></script> <script src="bundle1.js"></script> <script src="bundle2.js"></script> </body> </html>
上面的 commons.js 就是 main1.jsx 和 main2.jsx 的公共部分。也就是包含了 react 和 react-dom。
Demo13: Vendor chunk
你也可以用 CommonsChunkPlugin 提取第三方的 Js 生成一个单独的文件。
//main.js var $ = require('jquery') $('h1').text('Hello World') //index.html <html> <body> <h1></h1> <script src="vendor.js"></script> <script src="bundle.js"></script> </body> </html>
//webpack.config.js
var webpack = require('webpack')
module.exports = {
entry: {
app: './main.js',
vendor: ['jquery']
},
output: {
filename: 'bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.js'
})
]
}
上面的代码,entry.vendor: ['jquery'] 告诉 Webpack jeuery 应该被打包到一个公共 vendor.js 包中去。
如果你想让一个全局的变量在每一个模块可用,比如 $ 和 jQuery 不用 require("jquery") 就可以直接用。那么你需要使用 ProvidePlugin (Official doc) 这个插件,它可以自动的载入模块,而不需要到处 import 或者 require。
// main.js $('h1').text('Hello World') // webpack.config.js var webpack = require('webpack') module.exports = { entry: { app: './main.js' }, output: { filename: 'bundle.js' }, plugins: [ new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }) ] }
当然,这种情况,你还需要手动的全局的载入 jquery.js
Demo14: Exposing global variables
如果你想要用到一些全局变量,但是又不想在打包的时候,把它打包到 bundle 文件中去。这个时候你可以在 webpack.config.js 中配置 externals 字段。
比如,我们定义了一个 data.js 文件
// data.js var data = 'Hello World' //index.html <html> <body> <script src="data.js"></script> <script src="bundle.js"></script> </body> </html>
注意,这里 Webpack 只会打包 bundle.js, 而不会打包 data.js。
我们可以 data 字段暴露盛一个全局变量
//webpack.config.js module.exports = { entry: './main.jsx', output: { filename: 'bundle.js' }, module: { rules: [ { test: /\.js[x]?$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['es2015', 'react'] } } } ] }, externals: { // require('data') is external and available // on the global var data data: 'data' } }
现在,你可以在脚本中像引入其他模块一样引入 data。但它实际是一个全局变量。
// main.jsx var data = require('data') var React = require('react') var ReactDOM = require('react-dom') ReactDOM.render(<h1>{data}</h1>, document.body)
你同样可以把 react 和 react-dom 添加到 externals 中,这样可以大大缩短编译时间和编译后 bundle.js 文件的大小。