本文主要研究webpack3版本的react项目配置。
在普通开发者看来,webpack就是一个黑盒。
由一个或多个Entry(输入)
传入,最后输出一个或多个Output(输出)
。
webpack的内部具体是如何处理的呢?
Entry
: 输入。webpack从此处开始执行构建。Output
:输出。webpack最终处理输出的文件。Plugin
: 插件。webpack处理时抛出事件,插件接受事件处理额外的逻辑。Loader
:模块转换器。内容转换。Module
:模块。一个文件就是一个模块。Chunk
:块。一个块有一个或多个模块组成。Entry(输入)
文件当作一个Module(模块)
。Loaders
,稍后会讲)翻译Module
文件,并识别出Module
文件的所有依赖文件。Module
识别完之后,根据每个模块和Entry
的对应关系,组装成一个或多个Chunk
,并输出到输出列表。Output
的相关配置,输出到文件里。PLugin(插件)
会在自己感兴趣的事件后,执行特定逻辑。对于模块与模之间的联系,开发者通常会
- 使用script标签引入
- 通过commonjs(commonjs1,commonjs2)引入
- 通过AMD(require.js)引入
- 通过es6定义的模块引入方式引入
除了第一种方式,webpack都可以识别,作为文件依赖的标志,我们可以通过config配置,去支持或禁用哪一种引入方式。
假设我们写了这么一行代码:
import style from './index.css'
不好意思,webpack解析不了。
webpack原生不能解析css文件,目前webpack只能原生解析js文件,需要Loader
转换并插入到js文件。还有图片文件等等都需要Loader
支持。(具体配置见后文)
Plugin
可以大大扩展webpack的功能,哈哈。(具体配置见后文)
只要有输入,输出,webpack就能跑起来了。
// commonJS语法
const path = require(path);
const config = {
entry: 'app.js',
output: {
filename: '[name].js',
path: path.resolve(__dirname, '')
}
}
module.exports = config;
如果我们定义了多个入口文件,我们就会输出与入口文件数量对应的多个包。
生成的output文件本身是一个自执行函数,在webpack3版本,对于导出的文件还会进行Scope Hoisting
。
打个比方。
// a.js
import b from 'b.js'
document.write("hello" + b);
// b.js
export default 'world';
假设我们的a.js
是webpack的入口文件,a.js
文件依赖b.js
文件。webpack3打包之后会是这样:
// bundle.js
!function(){
....
}([function(e, t, r) {
"use strict";
r.r(t);
document.write("hello world")
}])
b
文件的作用域被提升了(webpack2就不会如此)。这会大大减小最后打包之后的体积。并且打包之后作用域变少,减小内存开销。
注: 只有es6代码可以被Scope Hoisting
,而且,如果一个文件被多个文件依赖,就不会使用Scope Hoisting
。
所以我们如果对于webpack打包的速度,打包后的体积不满意,可以试着更新webpack的版本哦。
如果您的项目使用了react-route
,webpack会对您的应用进行打包的时候,根据您的路由,打包成多个chunk输出。
比如: 我配置的路由有upload和plan两个路由。webpack会帮您打包成两个输出文件。实现按需加载。
生产环境,发布环境代码要不同的嘛。
所以会做环境的区分。
// webpack.config.js
const envDevelopment = project.env === 'development'
const envProduction = project.env === 'production'
// package.json
"dev": "cross-env NODE_ENV=development webpack -p --config webpack.config.js",
"prod": "cross-env NODE_ENV=production webpack -p --config webpack.config.js",
module.exports = { module: {
rules: [
test: /\.scss/ , // 使用正则表示,匹配文件名
use: [’style-loader’,’css-loader’,’sass-loader’]
]
}
}
对于use
,从后向前解析。先使用sass-loader
,再使用css-loader
解析,最后使用style-loader
解析。
sass-loader
会将sass文件转换成css文件。
css-loader
会解析css文件的依赖关系(@import),构建关于css的依赖。
style-loader
会将css注入js,动态向DOM注入样式。
可以使用ExtractTextPlugin
插件将css单独打包而不是注入js文件中。
postcss
是一个css处理平台。它是一个css编译器。平台有一系列css处理插件,比如autoprefixer
插件可以自动为css添加前缀-webkit- , --mos-
。
关于postcss的配置,可以直接在webpack的配置里写,也可以单独一个postcss.config.js
文件写。
// postcss.config.js
module.exports = {
options: {
plugins: [
require('autoprefixer')({
browsers: [
'ie >= 9',
'Edge >= 12'
]
})
]
}
};
vue有专门的vue-loader
。
react,es6代码可以使用babel转换。
typescript代码使用awesome-typescript-loader
。
类似postcss,babel是一个js编译器。它可以帮我们将代码转译成我们需要的js代码。
我们可以在.babelrc
里配置。
{
"presets": [
"es2015",
"react",
"stage-2"
],
"plugins": [
"transform-runtime",
"add-module-exports",
"transform-class-properties",
[
"import",
{
"libraryName": "antd",
"style": true
}
]
]
}
plugins
指定我们需要哪些插件。其中transform-runtime
是必须要装的,为了保证babel正常运行。
presets
告诉babel待转换的js代码都使用了哪些特性。
stage-2
是还未纳入ECMAScript标准的草案。
需要注意的是,我们在webpack的配置时
module.exports = {
module: {
rules: [{
test: /\.js$/,
use: ['babel-loader'],
}]
}
//输出 source-map 以方便直接调试 ES6 源码
devtool: 'source-map'
}
{
test: /\.(png|jpe?g|gif|svg|cur)?$/,
exclude: /node_modules/,
use: [{
loader: 'url-loader',
options: {
limit : 2560,
name: '[path][name].[ext]?v=[hash]'
}
}]
},
url-loader
可以将小于limit大小的图片转换成base64,嵌入到html文件中,减少网络请求。
还记得我们在介绍sass loader的时候,解析到css-loader之前,可以加入sprite-loader
。它可以将图片组成雪碧图,减少网络请求。
module.exports = { module: {
rules: [
test: /\.scss/ , // 使用正则表示,匹配文件名
use: [’style-loader’,’css-loader’,’sass-loader’]
]
}
}
UglifyJsPlugin
压缩代码插件,在prod打包模式下,需要将代码压缩。
webpack有一个秒杀其他打包工具的特性: tree-shaking
(虽然它也是借鉴来的)。tree-shaking
是webpack自带的功能,它可以检查无用代码,需要配合UglifyJsPlugin
在代码压缩的时候,剔除无用代码。
所以在dev模式,我的项目3mb,但是在prod模式,我的代码在压缩之后,只有200kb。(看来我引入了大的包,而且利用率不高呀)
webpack-parallel-uglify-plugin
随着项目越来越庞大,如果觉得UglifyJsPlugin
比较慢 ,可以使用webpack-parallel-uglify-plugin
,它可以使用多进程,并行压缩代码,速度显著提高。
CommonsChunkPlugin
如果有很多个文件都引入了一个文件,我们可以使用CommonsChunkPlugin
将重复的文件打包到一起,防止重复打包。
还有很多插件,在webpack官网上面可以找到。
我们希望我们的构建工具可以实时监听代码的变化,便于调试。
这里有webpack体系下的几种办法:
HotModuleReplacementPlugin
这家伙只能监听变化,不能刷新浏览器。
// webpack.config.js
export default = {
watch: true,
module.export = {
watchOptions : {
//不监听的 node modules 目录下的文件
ignored : /node_modules/,
}
}
}
不监听的 node modules 目录下的文件 ,会大大优化webpack速度。
// 或 package.json
'dev': "webpack --watch"
这家伙只能监听变化,不能刷新浏览器。
webpack-dev-server
这个插件会自动开启webpack的watch模式,而且会自动刷新浏览器。
多进程并行执行Loader
。
Loaders 和Plugins可以配置includes
和excludes
,可以缩小执行范围。
比如babel转换。
module: {
rules: [
{
test: /\.(js|jsx)?$/,
exclude: /node_modules/, // 去除node modules文件夹的文件
use: 'happypack/loader?id=jsx' // 这里使用了happypack
}]
}
动态链接库。
提前打包一些文件,在真实打包的时候,把他们链接进来,不需打包,直接引入。
其他优化方式前面提过
tree-shaking
和压缩代码,使代码体积变小CommonsChunkPlugin
单独打包重复使用的文件优势: