使用webpack构建项目
webpack核心概念
webpack 是一个现代 JavaScript 应用程序的模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成少量的 bundle - 通常只有一个,由浏览器加载。
环境依赖
node
项目初始化
创建项目
创建一个webpack-demo
项目并生成package.json
文件。之后所有操作的根目录均为webpack-demo/
mkdir webpack-demo
cd webpack-doemo
npm init -f
引入webpack
npm install --save-dev webpack
项目初始结构分析
通常项目会分成三个运行环境:开发人员在本地跑的开发环境(dev)
、测试人员用来做黑盒测试的测试环境(test)
和线上运行的生产环境(production)
。
简单起见,本文只考虑开发环境(dev)
和生产环境(prod)
,测试环境可以自行类比。
起步
webpack.config.js
//引入Node.js Path模块
const path = require('path);
module.exports = {
entry:'./src/index.js',// string | object | array
// 这里应用程序开始执行
// webpack 开始打包
output:{
filename:'bundle.js',// string
// 「入口分块(entry chunk)」的文件名模板(出口分块)
path:path.resolve(__dirname,'dist') // string
// 所有输出文件的目标路径
// 必须是绝对路径(使用 Node.js 的 path 模块)
}
}
- 直接编译
$ webpack
- 基于NPM 脚本(NPM Scripts)
$ npm run build
-
package.json
{ ... "scripts": { "build": "webpack" }, ... }
-
管理资源
加载CSS
安装style-loader
和css-loader
$ npm install --save-dev style-loader css-loader
src/index.js
+ impost './style.css';
webpack.config.js
const path = require('path');
module.exports = {
entry:'./src/index.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'dist')
},
module:{
rules:[
{
test:/\.css$/,
use:[
'style-loader',
'css-loader'
]
}
]
}
};
webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader。在这种情况下,以 .css 结尾的全部文件,都将被提供给 style-loader 和 css-loader。
加载图片
安装file-loader
$ npm install --seve-dev file-loader
index.js
+ impost Icon from './icon.png';
webpack.config.js
...
module:{
...
{
test:/\.(png|svg|gif|jpg)$/,
use:[
'file-loader'
]
}
}
...
加载字体,加载数据
管理输出
设定 HtmlWebpackPlugin
这个插件的作用是依据一个简单的index.html模板,生成一个自动引用你打包后的JS文件的新index.html。这在每次生成的js文件名称不同时非常有用(比如添加了hash值)。
$ npm install --save-dev html-webpack-plugin
webpack.config.js
...
plugins:[
new HtmlWebpackPlugin({
title: 'Output Management'
})
],
...
清理 /dist 文件夹
一个webpack插件在构建之前删除/清理你的构建文件夹
$ npm install clean-webpack-plugin --save-dev
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js'
},
plugins:[
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Output Management'
})
],
output: {
filename:'[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
};
开发
使用source map
对错误可追踪到源文件
webpack.config.js
module.exports = {
...
devtoll:'inline-source-map'
...
}
现在在浏览器打开最终生成的 index.html 文件,点击按钮,并且在控制台查看显示的错误。错误应该如下:
Uncaught ReferenceError: cosnole is not defined
at HTMLButtonElement.printMe (print.js:2)
选择一个开发者
webpack 中有几个不同的选项,可以帮助你在代码发生变化后自动编译代码:
- webpack's Watch Mode
- webpack-dev-server
- webpack-dev-middleware
多数情况使用webpack-dev-server
。
使用观察者模式
package.json
...
"scripts":{
"watch":"webpack --watch",
"build":"webpack"
}
...
使用wepack-dev-server
webpack-dev-server
提供一个简单的web
服务器,并且能实时重新加载(live reloading)。
$ npm install --save-dev webpack-dev-server
webpack.config.js
module.exports = {
...
devServer:{
contentBase:'./dist'
}
...
}
以上配置告知 webpack-dev-server,在 localhost:8080 下建立服务,将 dist 目录下的文件,作为可访问文件。
package
...
"scripts":{
"start":"webpack-dev-server --open"
}
...
使用webpack-dev-middleware
模块热替换
允许在运行时更新各种模块,而无需进行完全刷新。
启用HMR
webpack.config.js
const webpack = requier('webpack');
module.exports = {
entry:{
app:'./src/index.js'
},
devServer:{
contentBase:'./dist',
hot:true
}
...
plugins:[
new webpack.NamedChunksPlugin(),
new webpack.HotModuleReplacementPlugin()
//热加载插件
]
...
}
index.js
...
+ if (module.hot) {
+ module.hot.accept('./print.js', function() {
+ console.log('Accepting the updated printMe module!');
+ printMe();
+ })
+ }
HMR 修改样式表
webpack.config.js
+ module: {
+ rules: [
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader']
+ }
+ ]
+ },
index.js
+ import './styles.css';
Tree Shaking
用于描述移除
JavaScript
上下文中的未引用代码(dead-code
)。它依赖于ES2015
模块系统中的静态结构特性,例如import
和export
。
./src/mach.js
export function square(x){
return x * x;
}
export function cube(x){
return x * x * x;
}
index.js
import { cube } from "./math.js"
没有导出square
,但是它仍被包含在编译后的文件中。
精简输出
$ npm install --save-dev uglifyjs-webpack-plugin
webpack.config.js
const UglifyjsPlugin = require('uglifyjs-webpack-plugin')
...
plugins:[
new UglifyjsPlugin()
]
...
有复杂的依赖树的大型应用程序上运行时,tree shaking 或许会对 bundle 产生显著的体积优化。
生产环境构建
配置
开发环境(
development
)和生产环境(production
)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading
)或热模块替换(hot module replacement
)能力的source map
和localhost server
。而在生产环境中,我们的目标则转向于关注更小的bundle
,更轻量的source map
,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的webpack
配置。
安装
webpack-merge是专门用来处理webpack.config.js的配置文件分离的。它主要提供一个merge方法,来将各个分开的配置项给合并在一起
$ npm install --save-dev webpack-merge
project
webpack-demo
|- package.json
- |- webpack.config.js
+ |- webpack.common.js
+ |- webpack.dev.js
+ |- webpack.prod.js
|- /dist
|- /src
|- index.js
|- math.js
|- /node_modules
webpack.common.js
+ const path = require('path');
+ const CleanWebpackPlugin = require('clean-webpack-plugin');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
+
+ module.exports = {
+ entry: {
+ app: './src/index.js'
+ },
+ plugins: [
+ new CleanWebpackPlugin(['dist']),
+ new HtmlWebpackPlugin({
+ title: 'Production'
+ })
+ ],
+ output: {
+ filename: '[name].bundle.js',
+ path: path.resolve(__dirname, 'dist')
+ }
+ };
webpack.dev.js
+ const merge = require('webpack-merge');
+ const common = require('./webpack.common.js');
+
+ module.exports = merge(common, {
+ devtool: 'inline-source-map',
+ devServer: {
+ contentBase: './dist'
+ }
+ });
webpack.prod.js
+ const merge = require('webpack-merge');
+ const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
+ const common = require('./webpack.common.js');
+
+ module.exports = merge(common, {
+ plugins: [
+ new UglifyJSPlugin()
+ ]
+ });
Minification
注意,虽然 UglifyJSPlugin 是代码压缩方面比较好的选择,但是还有一些其他可选择项。以下有几个同样很受欢迎的插件:
- BabelMinifyWebpackPlugin
- ClosureCompilerPlugin
source map
webpack.prod.js
module.exports = merge(common, {
+ devtool: 'source-map',
plugins: [
- new UglifyJSPlugin()
+ new UglifyJSPlugin({
+ sourceMap: true
+ })
]
})
避免在生产中使用
inline-***
和eval-***
,因为它们可以增加 bundle 大小,并降低整体性能。
指定环境
CLI 替代选项
代码分离
代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
有三种常用的代码分离方法:
- 入口起点:使用 entry 配置手动地分离代码。
- 防止重复:使用 CommonsChunkPlugin 去重和分离 chunk。
- 动态导入:通过模块的内联函数调用来分离代码。
入口起点(entry points)
简单,直观。手动配置较多,并有些陷阱。