四、webpack高级概念
1. TreeShaking概念详解
用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。
webpack 4 正式版本,扩展了这个检测能力,通过 package.json 的 "sideEffects" 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 "pure(纯的 ES2015 模块)",由此可以安全地删除文件中未使用的部分。
TreeShaking只支持import的ES Module引入方式,不支持require的commonjs引入方式。因为import的底层是静态引入方式,而commonjs是动态引入方式。
// 开发环境
module.exports = {
mode: 'development',
optimization: {
usedExports: true // 只打包被使用的模块
}
}
package.json
不需要TreeShaking的写在package.json中
{
'sideEffects': ['@babel/polly-fill'], // 特殊要处理的内容
}
{
'sideEffects': false, // 正常的对所有模块进行TreeShaking,没有特殊要处理的东西
}
{
'sideEffects': [
'*.css'
],
}
npx webpack
// 上线环境
module.exports = {
mode: 'production', // production模式自带TreeShaking这些配置,就不需要optimization了
// optimization: {
// usedExports: true // 只打包被使用的模块
// }
}
配置TreeShaking非常简单
2. Development和Production模式的区分打包
拆分为三个文件:
-
webpack.common.js
-
webpack.dev.js
-
webpack.prod.js
可以将以上3个文件统一放到build文件夹下。
package.json
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"prod": "webpack --config webpack.prod.js",
}
webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const path = require('path')
const makePlugins = (configs) => {
const plugins = [
new CleanWebpackPlugin({
verbose: true // 运行时打印删除的文件 删除的路径与output的路径相同
})
]
Object.keys(configs.entry).forEach(item => {
let obj = {
template: './src/index.html',
filename: `${item}/index.html`,
chunks: [item],
inject: true
}
if (item === 'index') {
obj.filename = 'index.html'
}
plugins.push(
new HtmlWebpackPlugin(obj)
)
})
return plugins
}
const configs = {
entry: {
index: './src/index.js',
},
module: {
rules: [{
test: /\.(jsx|js)$/,
exclude: /node_modules/, // 排除
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', {
targets: {
chrome: "67"
},
useBuiltIns: 'usage' // 只引入用到的polyfill
}],
"@babel/preset-react"
],
plugins: ["@babel/plugin-syntax-dynamic-import"]
// "plugins": [["@babel/plugin-transform-runtime", {
// "absoluteRuntime": false,
// "corejs": 2,
// "helpers": true,
// "regenerator": true,
// "useESModules": false
// }]]
}
},{
test: /\.(jpg|png|gif)$/, //.jpg结尾的文件请求file-loader
use: {
loader: 'url-loader',
options: {
// placeholder 占位符
name: '[name]_[hash:5].[ext]', // 打包为原始的名字和后缀 看文档placerholder部分
outputPath: 'images/', //指定生成的文件目录
limit: 20480 // 如果图片超过了204800字节(20kb)打包为base64
}
}
},{
test: /\.(eot|ttf|svg|woff)$/,
use: {
loader: 'file-loader'
}
},{
test: /\.scss$/, //.jpg结尾的文件请求file-loader
use: [
'style-loader',
{ loader: 'css-loader',
options: {
importLoaders: 2,
modules: true, // 开启模块化打包,就可以用style.avatar的方式引入样式了
localIdentName: '[name]__[local]--[hash:base64:5]'
} },
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('autoprefixer')
]
}
},
'sass-loader'
]
},{
test: /\.css$/, //.jpg结尾的文件请求file-loader
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('autoprefixer')
]
}
}
]
}]
},
optimization: {
splitChunks: {
chunks: 'all', // 代码分割配置
cacheGroups: {
vendors: false,
default: false
}
}
},
output: {
// publicPath: '/', // 所有打包之前的引用都加一个 / 路径
// publicPath: 'https://cdn.com.cn',
filename: '[name].js', // 把打包出的bundle.js注入到html中
path: path.resolve(__dirname, '../dist')
}
}
configs.plugins = makePlugins(configs)
module.exports = configs
webpack.dev.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
// __dirname 指代webpack.config.js所在的当前目录的路径
const devConfig = {
mode: 'development', // 打包环境,默认为production
devtool: 'cheap-module-eval-soure-map',
devServer: {
contentBase: './dist', // 访问dist目录
open: true, // 自动打开浏览器
port: '8080',
hot: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
optimization: {
usedExports: true
},
}
module.exports = merge(commonConfig, devConfig)
webpack.prod.js
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
}
module.exports = merge(commonConfig, prodConfig)
npm install webpack-merge -D
3. webpack和Code Splitting之间的关系
Code Splitting就是代码分割
"scripts": {
"dev-build": "webpack --config ./build/webpack.dev.js"
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"prod": "webpack --config ./build/webpack.prod.js",
}
不用webpack配置实现:
webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const path = require('path')
const makePlugins = (configs) => {
const plugins = [
new CleanWebpackPlugin({
verbose: true // 运行时打印删除的文件 删除的路径与output的路径相同
})
]
Object.keys(configs.entry).forEach(item => {
let obj = {
template: './src/index.html',
filename: `${item}/index.html`,
chunks: [item],
inject: true
}
if (item === 'index') {
obj.filename = 'index.html'
}
plugins.push(
new HtmlWebpackPlugin(obj)
)
})
return plugins
}
const configs = {
entry: {
index: './src/index.js',
},
module: {
rules: [{
test: /\.(jsx|js)$/,
exclude: /node_modules/, // 排除
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', {
targets: {
chrome: "67"
},
useBuiltIns: 'usage' // 只引入用到的polyfill
}],
"@babel/preset-react"
],
plugins: ["@babel/plugin-syntax-dynamic-import"]
// "plugins": [["@babel/plugin-transform-runtime", {
// "absoluteRuntime": false,
// "corejs": 2,
// "helpers": true,
// "regenerator": true,
// "useESModules": false
// }]]
}
},{
test: /\.(jpg|png|gif)$/, //.jpg结尾的文件请求file-loader
use: {
loader: 'url-loader',
options: {
// placeholder 占位符
name: '[name]_[hash:5].[ext]', // 打包为原始的名字和后缀 看文档placerholder部分
outputPath: 'images/', //指定生成的文件目录
limit: 20480 // 如果图片超过了204800字节(20kb)打包为base64
}
}
},{
test: /\.(eot|ttf|svg|woff)$/,
use: {
loader: 'file-loader'
}
},{
test: /\.scss$/, //.jpg结尾的文件请求file-loader
use: [
'style-loader',
{ loader: 'css-loader',
options: {
importLoaders: 2,
modules: true, // 开启模块化打包,就可以用style.avatar的方式引入样式了
localIdentName: '[name]__[local]--[hash:base64:5]'
} },
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('autoprefixer')
]
}
},
'sass-loader'
]
},{
test: /\.css$/, //.jpg结尾的文件请求file-loader
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('autoprefixer')
]
}
}
]
}]
},
optimization: {
splitChunks: {
chunks: 'all', // 代码分割配置
cacheGroups: {
vendors: false,
default: false
}
}
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html', // 以src下的index.html作为模板生成html
}),
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../') // 根路径修改为当前文件夹的上一层
}) // 删除dist文件夹下的内容。只能删除根目录下的,不能删除根目录上级的内容
],
output: {
// publicPath: '/', // 所有打包之前的引用都加一个 / 路径
// publicPath: 'https://***.com',
filename: '[name].js', // 把打包出的bundle.js注入到html中
path: path.resolve(__dirname, '../dist')
}
}
configs.plugins = makePlugins(configs)
module.exports = configs
Code Splitting就是代码分割
npm install lodash --save
import _ from 'lodash';
console.log(_.join(['a', 'b', 'c'], '***'))
// 此处省略1000行业务逻辑
console.log(_.join(['d', 'e', 'f'], '***'))
此时打包的结果:lodash也被打包到了main.js中,打包生成的main.js的包就会非常大。 问题:打包文件大,加载时间长。而公共类库则基本不会变化。本地有缓存后,修改代码之后,则需要重新加载。
webpack.common.js
const makePlugins = (configs) => {
const configs = {
entry: {
lodash: './src/lodash.js',
main: './src/main.js',
},
optimization: {
splitChunks: {
chunks: 'all', // 代码分割配置
cacheGroups: {
vendors: false,
default: false
}
}
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html', // 以src下的index.html作为模板生成html
}),
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../') // 根路径修改为当前文件夹的上一层
}) // 删除dist文件夹下的内容。只能删除根目录下的,不能删除根目录上级的内容
],
output: {
// publicPath: '/', // 所有打包之前的引用都加一个 / 路径
// publicPath: 'https://***.com',
filename: '[name].js', // 把打包出的bundle.js注入到html中
path: path.resolve(__dirname, '../dist')
}
}
当业务逻辑发生变化时,只需要加载main.js即可。
以上的做法不够智能!
用webpack配置来实现:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all' // 帮我们做代码分割,遇到公用类库的时候自动打包生成文件
}
}
}
打包后的目录结构:生成了两个js,自动拆分了公共类库。
main.js
vendor~main.js //
通过合理的代码分割,让我们的项目运行效率更高。
另一种方法: 异步模块的引入。对异步加载的代码也会进行分割,会把库单独放到一个文件中。
function getComponent() {
return import('lodash').then((default: _ ) => {
let element = document.createElement('div')
element.innerHTML = _.join(['a', 'b'], '_')
return element
})
}
// 返回element
getComponent().then(element => {
document.body.appendChild(element)
})
上边的语法需要我们用babel来做转换
npm install babel-plugin-dynamic-import-webpack --save-dev
.babelrc
{
"presets": [
['@babel/preset-env', {
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
},
useBuiltIns: 'usage'
}
],
"@babel/preset-react"
],
"plugins": ["dynamic-import-webpack"]
}
总结:
- 代码分割和webpack无关,是单独的一个概念,来提高代码的性能。
- webpack有两种实现代码分割的方式:同步、异步
- 同步代码:只需要在webpack.common.js中做optimization的配置即可
- 异步代码:无需做任何配置,会自动进行代码分割,放置到新的文件中
4. splitChunks配置参数详解
主要作用:将所有的第三方模块(node_modules)中的文件都打包到vendor.js文件中。
移除 babel-plugin-dynamic-import-webpack 插件,不支持魔法注释的写法。"plugins": ["dynamic-import-webpack"]也移除。
使用官方提供的插件。babeljs.io/docs/en/bab…
plugin-syntax-dynamic-import插件作用:插件语法动态导入。webpack4默认是允许import动态导入的,但是需要babel的插件支持,目前babel的插件包为:@babel/plugin-syntax-dynamic-import,动态加载的最大好处就是实现了懒加载,用到哪个模块才会加载那个模块,可以提高SPA应用程序的首屏加载速度。
npm install --save-dev @babel/plugin-syntax-dynamic-import
修改.babelrc
{
"presets": [
['@babel/preset-env', {
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
},
useBuiltIns: 'usage'
}
],
"@babel/preset-react"
],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
此时就可以使用魔法注释了/* webpackChunkName:"lodash" */,参考文档:Magic Comments
function getComponent() {
// 调用 import() 之处,被作为分离的模块起点,意思是,被请求的模块和它引用的所有子模块,会被分离到一个单独的chunk中。
return import(/* webpackChunkName:"lodash" */ 'lodash').then((default: _ ) => { // 魔法注释
let element = document.createElement('div')
element.innerHTML = _.join(['a', 'b'], '_')
return element
})
}
// 返回element
getComponent().then(element => {
document.body.appendChild(element)
})
打包出的文件名变为:vendors~lodash.js
webpackChunkName:新 chunk 的名称。从 webpack 2.6.0 开始,[index] and [request] 占位符,分别支持赋予一个递增的数字(1、2、3...)和实际解析的文件名。 因此也可以用/* webpackChunkName: "[request]" */这种占位符的方式来写。
参考文档:webpack.docschina.org/plugins/spl…
cacheGroups:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: false,
default: false
}
}
}
}
splitChunks的默认配置:
This configuration object represents the default behavior of the SplitChunksPlugin.
// splitChunks的默认配置
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', // 只对异步代码的代码分割生效,对同步引入的代码库就不生效,参数: async、initial(同步代码分割)、all,all的时候需要配置cacheGroups参数
minSize: 30000, // 引入的模块、包只有大于3000个字节(30KB)才会做代码分割
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/, // 检测引入的库是否是在node_modules中,如果是,则代码分割,就会打包到vendors这个组里,生成vendors~***.js,vendors代表符合这个组vendors的要求,***.js代表分割出的代码实际的入口文件名,如:main.js则为vendors~main.js
priority: -10,
filename: 'vendors.js', // 这样配置就全部打包生成到了vendors.js中,不再分多个vendors
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
打包同步的代码,不仅仅会走chunks: 'initial'这个配置,还要走cacheGroups这个配置规则。
同步引入import,异步引入promise。
// splitChunks的默认配置
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', // 只对异步代码的代码分割生效,对同步引入的代码库就不生效,参数: async、initial(同步代码分割)、all,all的时候需要配置cacheGroups参数
minSize: 30000, // 引入的模块、包只有大于3000个字节(30KB)才会做代码分割
maxSize: 50000, // 50KB, lodash 1MB,使用较少
minChunks: 2, // 当一个模块至少使用了n次之后就会进行代码分割,例如:为2时,则不会进行代码分割
maxAsyncRequests: 5, // 同时加载的模块数,最多分割5个,大于5个时就不会分割
maxInitialRequests: 3, // 整个网站首页加载时,入口文件引入其他js文件或库, 入口文件,按默认配置即可
automaticNameDelimiter: '~', // 组和文件之间的链接符vendors~main.js
name: true, // 打包生成的名字在cacheGroups中生效
cacheGroups: { // 缓存组,把所有符合的组都先缓存,再分析
vendors: {
test: /[\\/]node_modules[\\/]/, // 检测引入的库是否是在node_modules中,如果是,则代码分割,就会打包到vendors这个组里,生成vendors~***.js,vendors代表符合这个组vendors的要求,***.js代表分割出的代码实际的入口文件名,如:main.js则为vendors~main.js
priority: -10, // 判断vendors和default放到哪个组中,如果两个组都符合条件,值越大优先级就越高,就放到哪个组中,你可以思考一下为什么要有这个参数
filename: 'vendors.js', // 这样配置就全部打包生成到了vendors.js中,不再分多个vendors
},
default: { // 默认存放位置
minChunks: 2,
priority: -20,
reuseExistingChunk: true, // 看之前代码是否已经被引入过打包过,则会忽略这个模块,直接使用之前已经打包过的模块
filename: 'common.js'
}
}
}
}
}
如果是自己写的模块,由于不在node_modules文件夹下,则走default规范,生成的文件为default~main.js
最难的模块:
cacheGroups上边的代码有效则不会走到cacheGroups分组中。
参考文档:webpack.docschina.org/plugins/spl… 写一些相关split-chunks-plugin的分享,使用心得
5. Lazy Loading懒加载,Chunk是什么?
Lazy Loading是什么
参考文档:webpack.js.org/guides/lazy…
写法1:
function getComponent() {
return import(/* webpackChunkName:"lodash" */ 'lodash').then((default: _ ) => { // 魔法注释
let element = document.createElement('div')
element.innerHTML = _.join(['a', 'b'], '_')
return element
})
}
// 放进click事件中,点击页面的任何地方,loadsh才会被加载
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element)
})
})
通过import方式可以对某些模块进行懒加载Lazy Loading,在访问页面的时候不会加载,在执行某个事件的时候,才会被加载。懒加载并不是webpack的概念,webpack只不过是能识别import的语法,对其引入的模块进行代码分割。import返回的实际是一个promise语法,因此需要babel-polyfill。新版本babel内置babel-polyfill。
官方说明:https://babeljs.io/docs/en/babel-polyfill
As of Babel 7.4.0, this package has been deprecated in favor of directly including core-js/stable (to polyfill ECMAScript features) and regenerator-runtime/runtime (needed to use transpiled generator functions)
优点:可以让我们的页面加载更快。
异步函数,可以省略掉promise比较复杂的写法,改为async await。
写法2:
async function getComponent() {
const {default: _} = await import(/* webpackChunkName:"lodash" */ 'lodash')
const element = document.createElement('div')
element.innerHTML = _.join(['a', 'b'], '_')
return element
}
// 放进click事件中,点击页面的任何地方,loadsh才会被加载
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element)
})
})
Chunk是什么
对网站功能进行划分,每一类一个chunk。
在webpack中,生成了几个文件,就是几个Chunk,也就是打包片段。如下图:

minChunks参数:当一个模块至少使用了n次之后就会进行代码分割,例如:为2时,则不会进行代码分割,默认为1.
6. 打包分析,preloading,prefetching
打包分析:在用webpack进行代码打包之后,借助打包分析工具,对我们打包生成的问题件进行分析,进而查看打包是否合理。 参考网站:github.com/webpack/ana…
// package.json
"scripts": {
"dev-build": "webpack --profile --json > stats.json --config ./build/webpack.prod.js"
}
npm run dev-build // 进行打包,生成了`stats.json`文件
webpack.github.io/analyse/ 打开后,上传stats.json,可进行不同模块分析。
还有其他很多分析工具。如:
参考文档:webpack.docschina.org/guides/code…
preloading,prefetching: webpack.docschina.org/guides/code…
optimization: {
splitChunks: {
chunks: 'async', // 默认值,默认只对异步代码进行分割
}
}
首次访问页面,加载速度就是最快的。
在浏览器控制台,command+shift+p,输入coverage,选择show coverage,点击录制按钮,可以查看页面代码的利用率,进而对代码进行分析,改进。重点考虑的并不是代码缓存的问题,而是代码的使用率。把首次进入页面不执行的代码进行拆分。

在空闲时间加载不是第一次就要执行的代码,比如模态框等。就满足了首页加载快的问题,也满足了再使用时再加载反应慢的问题。这个解决方案就依赖preloading,prefetching。
使用magic commont语法,放到import语句中,/* webpackPrefetch: true */表示主要js加载完成之后,网络带宽有空闲的时候,才会加载此js。类似于<script async ></script>的async属性。
// prefetching
import(/* webpackPrefetch: true */ 'LoginModal');
// preloading
import(/* webpackPreload: true */ 'ChartingLibrary');
prefetching与preloading的区别:prefetching是主流程加载完之后才会加载,preloading是和主业务文件一起加载。
最优编码方式:缓存带来的代码性能提升非常有限,重点考虑如何让页面加载的js文件代码的利用率最高。交互之后才用到的代码可以写到异步组件中,通过懒加载把代码逻辑加载进来,这样才会提升页面性能。因为懒加载导致的反应慢的问题,可以用prefetching、preloading来解决。
前端代码性能优化,要把重点放在code coverage代码覆盖率上,缓存并不是最重要的点。
7. css文件的代码分割
webpack.common.js
output: {
filename: '[name].js', // 入口文件
chunkFilename: '[name].chunk.js', // 不是入口文件则走这个chunkFilename
}
生成的js文件
main.js
vendors~lodash.chunk.js
参考文档:webpack.docschina.org/configurati…
css文件的代码分割
参考文档: webpack.docschina.org/plugins/min…
默认css会直接打包到js中,借助mini-css-extract-plugin可单独打包为css文件,此包暂不支持热更新。因此此插件一般用在线上环境打包时使用。
// webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
"css-loader"
]
}
]
},
plugin: [
new MiniCssExtractPlugin({
filename: '[name].css', // 被页面直接引用,走这个名字
chunkFilename: '[name].chunk.css',
}),
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.optimize\.css$/g,
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
preset: ['default', { discardComments: { removeAll: true } }],
},
canPrint: true
})
]
}
module.exports = merge(commonConfig, prodConfig)
要使用这个插件,还需要对loader进行配置。之前是通过style-loader将css样式挂载到页面上,现在要改为使用此插件来单独生成一个文件。
参考文档:github.com/NMFR/optimi… 压缩css
MiniCssExtractPlugin中也借助splitChunks
知识点:
- filename和chunkFilename要搞清楚
mini-css-extract-plugin这个插件只能用在线上环境,因为不支持HMR
8. webpack与浏览器缓存(caching)
contenthash
// webpack.prod.js
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
}
webpack版本较老时:
optimization: {
runtimeChunk: {
name: entrypoint => `runtime~${entrypoint.name}`
}
},
main.js放业务逻辑,vendors.js放引入的库。业务逻辑和库之间也是有关联的,关联都放在了,manifest内置了包和包之间的关系,runtime.js抽离出来中放对应manifest相关的关系。
9. Shimming(垫片)的作用
babel-polyfill的作用:在低版本浏览器上的兼容问题。垫片。
webpack是基于模块打包的,webpack的这些变量只能在模块这一个文件中被使用。在另一个文件中就无法使用这个模块的变量。
// webpack.common.js
const webpack = require('webpack')
plugins: [
new webpack.ProvidePlugin({
$: 'jquery' // 如果在代码的模块中使用了$这个关键字,则会自动引入jquery,把模块的名字命名为$。 import $ from 'jquery',在配置里帮我们解决了
_: 'lodash', // 也可以写成这样: _join: ['lodash', 'join']
})
],
代码如下依旧可以正常编译:
export function ui() {
$('body').css('background', _.join(['blue'], ''))
}
export function ui() {
$('body').css('background', _join(['blue'], ''))
}
// this指向问题
console.log(this)
console.log(this === window) // 一个模块里的this默认指向模块自身,而不是window
npm install imports-loader --save-dev // 修改webpack默认行为,就是shimming
使用多个loader
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader' // 2.在用babel-loader做文件编译
}, {
loader: 'imports-loader?this=>window' // 1.先 this一直指向window
}] ,
}
]
}
shimming这个概念很宽泛,遇到不同场景,找对应的解决方法。
参考文档: webpack.docschina.org/guides/shim…
10. 环境变量的使用
只需要一个common.js文件通过在package.json中传递不同的参数,区分是开发环境还是生产环境。
这种写法并不常用,只是为了让我们理解webpack中全局变量的概念
// package.json
{
"name": "***",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev-build": "webpack --config ./build/webpack.common.js",
"dev": "webpack-dev-server --config ./build/webpack.common.js",
"build": "webpack --env.production --config ./build/webpack.common.js" //通过--env.production,把环境变量传进去
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.2.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.2.0",
"@babel/preset-react": "^7.0.0",
"autoprefixer": "^9.3.1",
"babel-loader": "^8.0.4",
"clean-webpack-plugin": "^1.0.0",
"css-loader": "^1.0.1",
"express": "^4.16.4",
"file-loader": "^2.0.0",
"html-webpack-plugin": "^3.2.0",
"imports-loader": "^0.8.0",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.10.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"postcss-loader": "^3.0.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack-cli": "^3.1.2",
"webpack-dev-middleware": "^3.4.0",
"webpack-dev-server": "^3.1.10",
"webpack-merge": "^4.1.5"
},
"dependencies": {
"@babel/polyfill": "^7.0.0",
"@babel/runtime": "^7.2.0",
"@babel/runtime-corejs2": "^7.2.0",
"jquery": "^3.3.1",
"lodash": "^4.17.11",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"webpack": "^4.25.1"
}
}
// webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const merge = require('webpack-merge');
const devConfig = require('./webpack.dev.js');
const prodConfig = require('./webpack.prod.js');
const commonConfig = {
entry: {
main: './src/index.js',
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader'
}, {
loader: 'imports-loader?this=>window'
}]
}, {
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240
}
}
}, {
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader'
}
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../')
}),
new webpack.ProvidePlugin({
$: 'jquery',
_join: ['lodash', 'join']
}),
],
optimization: {
runtimeChunk: {
name: 'runtime'
},
usedExports: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors',
}
}
}
},
performance: false,
output: {
path: path.resolve(__dirname, '../dist')
}
}
module.exports = (env) => {
if(env && env.production) {//线上环境
return merge(commonConfig, prodConfig);
}else {//开发环境
return merge(commonConfig, devConfig);
}
}
// webpack.dev.js
const webpack = require('webpack');
const devConfig = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist',
open: true,
port: 8080,
hot: true
},
module: {
rules: [{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'postcss-loader',
'sass-loader',
]
}, {
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}]
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
output: {
filename: '[name].js',
chunkFilename: '[name].js',
}
}
module.exports = devConfig;
// webpack.prod.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
module: {
rules:[{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'postcss-loader',
'sass-loader',
]
}, {
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}]
},
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css'
})
],
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
}
}
module.exports = prodConfig;
若 --env.production改为--env.production===abc,则使用方法如下:
module.exports = (env) => {
if(env && env.production === 'abc') {//线上环境
return merge(commonConfig, prodConfig);
}else {//开发环境
return merge(commonConfig, devConfig);
}
}
11. 使用EnvironmentPlugin来设置环境变量
参考文档:www.webpackjs.com/plugins/env…
EnvironmentPlugin 是一个通过 DefinePlugin 来设置 process.env 环境变量的快捷方式。
使用示例: 当我们需要在项目中添加埋点,需要将相应的版本号信息上报的时候,就可以这么做:
// 引入项目版本号
const { version } = require('../package.json')
const { EnvironmentPlugin } = require('webpack')
new EnvironmentPlugin({
PKG_VERSION: version,
})
这样就将 PKG_VERSION注入到了环境变量中
使用:
// xxx.js
release_version: process.env.PKG_VERSION
webpack编译后的效果为(假设package.json中版本号为0.1.0):
// xxx.js
release_version: '0.1.0'
这样就将环境变量引入到了项目文件中!