一、PostCSS插件自动补齐CSS3前缀

1.1 背景

CSS3属性为什么有前缀?因为浏览器标准没有统一。

比如下面,写起来很慢,低效,麻烦。

.box {-moz-border-radius: 10px;-webkit-border-radius: 10px;-o-border-radius: 10px;border-radius: 10px;
}

1.2 解决

autoprefixer,css的后置处理器,这个autoprefixer和less、sass不一样,less、sass是预处理器,预处理器是打包前处理,而autoprefixer是后置处理器,在样式处理好后,代码最终生成完了之后,再进行后置处理。
根据Can I Use规则(https://caniuse.com/)
autoprefixer通常和postcss-loader一起用

npm install postcss-loader autoprefixer -D

我们在less文件中写一个display: flex; 然后看编译之后是否带了各种前缀

全文配置:

const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')module.exports = {entry: {index: './src/index.js',search: './src/search.js'},output: {path: path.join(__dirname, 'dist'),filename: '[name]_[chunkhash:8].js'},mode: 'production',module: {rules: [{test: /\.js$/,use: 'babel-loader'},{test: /\.css$/,use: [// 'style-loader',MiniCssExtractPlugin.loader,'css-loader']},{test: /\.less$/,use: [// 'style-loader',MiniCssExtractPlugin.loader,'css-loader','less-loader',{loader: 'postcss-loader',options: {plugins: () => [require('autoprefixer')({browsers: ['last 2 version', '>1%', 'ios 7']})]}}]},{test: /\.(png|jpg|gif|jpeg)$/,use: [{loader: 'file-loader',options: {name: '[name]_[hash:8].[ext]'}}]},{test: /\.(woff|woff2|eot|ttf|otf)$/,use: [{loader: 'file-loader',options: {name: '[name]_[hash:8].[ext]'}}]}]},plugins: [new CleanWebpackPlugin(),new MiniCssExtractPlugin({filename: '[name]_[contenthash:8].css'}),new OptimizeCSSAssetsPlugin({assetNameRegExp: /\.css$/g,cssProcessor: require('cssnano')}),// 这里多页面,需要写两个new HtmlWebpackPlugin({template: path.join(__dirname, 'src/index.html'),filename: 'index.html',chunks: ['index'],inject: true,minify: {html5: true,collapseWhitespace: true,preserveLineBreaks: false,minifyCSS: true,minifyJS: true,removeComments: false}}),new HtmlWebpackPlugin({template: path.join(__dirname, 'src/search.html'),filename: 'search.html',chunks: ['search'],inject: true,minify: {html5: true,collapseWhitespace: true,preserveLineBreaks: false,minifyCSS: true,minifyJS: true,removeComments: false}})]
}

结果如下,不负众望。

但是我们看到了一下警告:


二、移动端CSS px转换为rem

2.1 分辨率


我们可以借助媒体查询去适配,但是缺陷是要兼顾很多,需要多套适配样式代码

@media screen and (max-width: 980px) {.header {width: 900px;}
}
@media screen and (max-width: 480px) {.header {height: 400px;}
}
@media screen and (max-width: 350px) {.header {height: 300px;}
}

2.2 rem是什么

w3c对rem的定义: font-size of the root element
rem和px的对比:
rem: 相对单位
px:绝对单位
说一下我个人的理解吧。
px:理解为将屏幕分成的每一份的“长度”,比如,360px,就是将该设备宽度平均分成360份。我们拿到设计稿之后,设计稿只有一个数值,比如750px的设计稿,但是我们移动端的手机可能是360px,可能是414的,可能是460的,375的,等等。我们总不能都按照750设计稿上的数字去写吧。
所以,我们把750px的设计稿理解为设计师把设备平均分成750份,比如设计稿里面某个div宽度是100px,我们理解为这个div占了750份里面的100份。
那么,推广应用一下,无论我们的设备是多少分辨率的,也就是宽度不管是360的,还是414的,我们只要获取到屏幕宽度即可window.screen.width,或者还有其他方法来获取,具体看情况吧,反正肯定能拿到手机屏幕的宽度。我们令 屏幕宽度 screenWidth = window.screen.width吧。我们按照设计师的意图,将屏幕宽度分成750份。那每一份就是 screenWidth / 750 , 那么设计稿里面100px的div实际宽度就是 100 * (screenWidth/750), 其实每次这么算其实也是很不方便的,因为可能出现一些奇奇怪怪的数字,让你不能一眼看出到底是多大。
我们已经知道,每一份是 screenWidth/750,这个数值可能是个小数,是个奇奇怪怪的数字,不好计算,我们进一步想想,我们可以把多份合在一起,合成一个比较整或者比较好计算的数字,比如,我们可以把75份合在一起, 那就是 75 * (screenWidth/750) = screenWidth/10, 我们把这个数字叫做根字号,可见根字号是可以自己设计定制的,你可以根据自己的喜好来规定。

根字号 = x * (screenWidth/750)

其中,x就是x份的屏幕宽度,我们把这个x份叫做一个rem。也就是1个rem包含了多少份。因为,x的值你可以根据自己的喜好来定,所以,这个rem也是一个很灵活的值。另外,说明一点,浏览器默认的值是1rem为16份。

2.3 解决方案

使用px2rem-loader,将px转换成rem,px转换成rem之后,我们需要知道一个rem到底等于多少个px?这个时候,我们需要在页面打开的时候,动态计算根元素的font-size的值。我们可以利用手机淘宝里一个很成熟的方案,是一个库,lib-flexible。这个库能自动根据当前的设备的宽高,自动计算根元素的实际font-size值。
https://github.com/amfe/lib-flexible

npm i px2rem-loader -D
npm i lib-flexible -S


我们先运行一下:

接下来,我们要将根元素相对的rem计算出来,也就是根元素,html这个结点在实际的设备分辨率中的font-size大小。这个时候,我们需要使用lib-flexible,但是,我们现在要把这个库引进来,暂时手动将这个代码引进来吧。
在src/search.html中加入.我们在node_modules里找到 lib-flexible这个库,将里面的源代码复制粘贴到search.html的

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title><script type="text/javascript">;(function(win, lib) {var doc = win.document;var docEl = doc.documentElement;var metaEl = doc.querySelector('meta[name="viewport"]');var flexibleEl = doc.querySelector('meta[name="flexible"]');var dpr = 0;var scale = 0;var tid;var flexible = lib.flexible || (lib.flexible = {});if (metaEl) {console.warn('将根据已有的meta标签来设置缩放比例');var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);if (match) {scale = parseFloat(match[1]);dpr = parseInt(1 / scale);}} else if (flexibleEl) {var content = flexibleEl.getAttribute('content');if (content) {var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);if (initialDpr) {dpr = parseFloat(initialDpr[1]);scale = parseFloat((1 / dpr).toFixed(2));}if (maximumDpr) {dpr = parseFloat(maximumDpr[1]);scale = parseFloat((1 / dpr).toFixed(2));}}}if (!dpr && !scale) {var isAndroid = win.navigator.appVersion.match(/android/gi);var isIPhone = win.navigator.appVersion.match(/iphone/gi);var devicePixelRatio = win.devicePixelRatio;if (isIPhone) {// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {dpr = 3;} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){dpr = 2;} else {dpr = 1;}} else {// 其他设备下,仍旧使用1倍的方案dpr = 1;}scale = 1 / dpr;}docEl.setAttribute('data-dpr', dpr);if (!metaEl) {metaEl = doc.createElement('meta');metaEl.setAttribute('name', 'viewport');metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');if (docEl.firstElementChild) {docEl.firstElementChild.appendChild(metaEl);} else {var wrap = doc.createElement('div');wrap.appendChild(metaEl);doc.write(wrap.innerHTML);}}function refreshRem(){var width = docEl.getBoundingClientRect().width;if (width / dpr > 540) {width = 540 * dpr;}var rem = width / 10;docEl.style.fontSize = rem + 'px';flexible.rem = win.rem = rem;}win.addEventListener('resize', function() {clearTimeout(tid);tid = setTimeout(refreshRem, 300);}, false);win.addEventListener('pageshow', function(e) {if (e.persisted) {clearTimeout(tid);tid = setTimeout(refreshRem, 300);}}, false);if (doc.readyState === 'complete') {doc.body.style.fontSize = 12 * dpr + 'px';} else {doc.addEventListener('DOMContentLoaded', function(e) {doc.body.style.fontSize = 12 * dpr + 'px';}, false);}refreshRem();flexible.dpr = win.dpr = dpr;flexible.refreshRem = refreshRem;flexible.rem2px = function(d) {var val = parseFloat(d) * this.rem;if (typeof d === 'string' && d.match(/rem$/)) {val += 'px';}return val;}flexible.px2rem = function(d) {var val = parseFloat(d) / this.rem;if (typeof d === 'string' && d.match(/px$/)) {val += 'rem';}return val;}})(window, window['lib'] || (window['lib'] = {}));</script>
</head>
<body><div id="root"></div>
</body>
</html>

npm run build之后,打开 dist/search.html文件


看到了吗,字体也会随着屏幕分辨率的变化而变化

2.4 答疑

2.4.1 这个最大的缺点就是会把第三方ui库的px也给转了
答: 这个px2rem-loader 也是可以设置 exclude 的,可以把 node_modules 里面的模块 exclude 掉。
另外如果不设置 exclude,那么也可以使用 /no/的语法去设置某一行样式不进行 px2rem 的转换操作。
可以用 /no/ 这种注释语法。比如:

.page {font-size: 12px; /*no*/width: 375px; /*no*/height: 40px;
}

后面有 /no/这种注释语法会不进行 rem 的转换
2.4.2 按照750的设计稿,直接就是10px写10px吗?为什么看着比设计稿的要大?
答: 如果设置的 remUnit 是 75,那么对于 750 的设计稿如果字体是 24px,就写 24px(实际上在 iphone 6是12px的大小)
如果设置的 remUnit 是 37.5,那么对于 375的设计稿如果字体是 12px,就写 12px(实际上在 iphone 6是12px的大小)
看着比设计稿的要大这个需要以 iphone 6为参照哈
2.4.3 flexible.js作用是什么?使用px2rem-loader,就必须和flexible.js一起搭配使用吗?
答:必须一起使用。
px2rem-loader 只是以构建的手段将 px 单位转换成了 rem。但是 rem 和 px 的单位计算并不清楚,flexible.js 的作用就是动态的去计算不同设备下的 rem 相对 px 的单位,也就是计算跟元素 html 节点的 font-size 大小

三、静态资源内联

上面我们已经发现,为了引入一个lib-flexible,我们还要将代码复制粘贴到HTML文件中,这显然是不符合现代开发逻辑的,不够自动化。
内联,比如我们怎么将css 代码、js代码内联到HTML中,怎么将图片、字体内联到代码中等

3.1 内联的意义

  1. 页面框架的初始化脚本,比如上一节里面lib-flexible,项目初始化就要执行的脚本。
  2. 上报相关打点,css初始化和css加载完成这些上报点的代码都要内联到HTML中。
  3. css内联避免页面闪动。
  4. 减少http网络请求数,小图片或者字体内联url-loader

3.2 raw-loader内联html

比如要把一段html片段内联进去,这个html片段是移动端开发要用的meta信息片段,很大一段,很多页面都要用到。

<script>${require('raw-loader!babel-loader!./xx.js')}</script>

我们准备了一个meta.html文件,用于内联到其他html文件中,尤其是做一些手机端网页,都需要一些meta信息。

<meta charset="UTF-8">
<meta name="viewport" content="viewport-fit=cover,width=device-width,initial-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<meta name="keywords" content="now,now直播,直播,腾讯直播,QQ直播,美女直播,附近直播,才艺直播,小视频,个人直播,美女视频,在线直播,手机直播">
<meta name="name" itemprop="name" content="NOW直播—腾讯旗下全民视频社交直播平台"><meta name="description" itemprop="description" content="NOW直播,腾讯旗下全民高清视频直播平台,汇集中外大咖,最in网红,草根偶像,明星艺人,校花,小鲜肉,逗逼段子手,各类美食、音乐、旅游、时尚、健身达人与你24小时不间断互动直播,各种奇葩刺激的直播玩法,让你跃跃欲试,你会发现,原来人人都可以当主播赚钱!">
<meta name="image" itemprop="image" content="https://pub.idqqimg.com/pc/misc/files/20170831/60b60446e34b40b98fa26afcc62a5f74.jpg"><meta name="baidu-site-verification" content="G4ovcyX25V">
<meta name="apple-mobile-web-app-capable" content="no">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<link rel="dns-prefetch" href="//11.url.cn/">
<link rel="dns-prefetch" href="//open.mobile.qq.com/">
npm install raw-loader@0.5.1 -D

我们在src/search.html模板修改如下,不再使用

<!DOCTYPE html>
<html lang="en">
<head>${ require('raw-loader!./meta.html') }<title>Document</title><script>${require('raw-loader!babel-loader!../../node_modules/lib-flexible/lib-flexible.js')}</script>
</head>
<body><div id="root"></div>
</body>
</html>

四、多页面打包通用方案

4.1 什么是多页面MPA

发布上线后有很多个入口,一个页面就是一个业务,单页面应用就是只有一个入口,所有的业务都放在一个入口,不同的业务用同一个url,只是后面hash不一样。多页应用好处就是多个页面解耦,而且对SEO友好。
每个页面对应一个entry,一个html-webpack-plugin,缺点是每次新增或者删除页面需要改webpack配置。

4.2 解决方案

动态获取entry和设置html-webpack-plugin数量
利用glob.sync

entry: glob.sync(path.join(__dirname, './src/*/index.js'))

传统的是:

module.exports = {entry: {index: './src/index/index.js',search: './src/search/index.js'}
}

当然这样做,我们需要一些约定

  • 都放在src下
  • 不同的目录的出口文件都命名为index.js

就是利用glob库

entry: glob.sync(path.join(__dirname, './src/*/index.js'))

__dirname就是项目根目录,根目录下的src下匹配src下所有一级目录,然后继续匹配到下面的index.js,这样所有的entry都动态的获取出来了,我们根据这个数量动态的设置webpack中 html-webpack-plugin的数量。

4.3 操作举例

4.3.1 修改目录和文件名

新建两个目录:index 和 search,把属于他们的文件都move到相应的目录中,同时,入口文件都修改为index.js.

4.3.2 安装 glob

npm install glob -D

4.3.3 在webpack中改写

const glob = require('glob')// 动态设置多页面的函数
const setMPA = () => {const myEntry = {}const myHtmlWebpackPlugin = []const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'))// entryFiles =  [//   '/Users/guoyu/work/webpack-demo/src/index/index.js',//   '/Users/guoyu/work/webpack-demo/src/search/index.js'// ]Object.keys(entryFiles).forEach(index => {const entryFile = entryFiles[index]const match = entryFile.match(/src\/(.*)\/index\.js/)// index ===  0// match ==  [//   'src/index/index.js',//   'index',//   index: 31,//   input: '/Users/guoyu/work/webpack-demo/src/index/index.js',//   groups: undefined// ]// index ===  1// match ==  [//   'src/search/index.js',//   'search',//   index: 31,//   input: '/Users/guoyu/work/webpack-demo/src/search/index.js',//   groups: undefined// ]const pageName = match && match[1]myEntry[pageName] = entryFilemyHtmlWebpackPlugin.push(new HtmlWebpackPlugin({template: path.join(__dirname, `src/${pageName}/index.html`),filename: `${pageName}.html`,chunks: [pageName],inject: true,minify: {html5: true,collapseWhitespace: true,preserveLineBreaks: false,minifyCSS: true,minifyJS: true,removeComments: false}}))})return {entry: myEntry,htmlWebpackPlugin: myHtmlWebpackPlugin}
}// 直接调用
const { entry, htmlWebpackPlugin } = setMPA()module.exports = {entry: entry,plugins: [new CleanWebpackPlugin(),new MiniCssExtractPlugin({filename: '[name]_[contenthash:8].css'}),new OptimizeCSSAssetsPlugin({assetNameRegExp: /\.css$/g,cssProcessor: require('cssnano')})].concat(htmlWebpackPlugin) // 填充到webpack的plugins中
}

4.3.4 运行结果


五、使用source map

5.1 简介

作用:通过source map定位到源代码,可以看看阮一峰老师的科普文。
开发环境开启,线上环境关闭,因为如果线上环境开启会暴露业务逻辑,是不安全的。
线上排查的时候,可以把sourcemap上传到错误监控系统。
eval: 使用eval包裹模块代码
sourcemap: 产生.map文件
cheap:不包括列信息
inline:将.map做为DataURL嵌入,不单独产生.map文件
module:包含loader的source map
source map 类型如下,其实就是上面五种的各种组合。

5.2 操作实践

先把webpack中的mode设置为none,因为如果是production,会压缩,不利于观察

  mode: 'none'

再在webpack中添加一项

devtool: 'eval'

npm run build之后观察打包出来的文件


等等,有很多模块,很多个eval
我们可以看到每个eval后面有个sourceURL,指明了编译的是哪个模块
eval 换成 source-map

devtool: 'source-map'


换成 inline-map

devtool: 'inline-source-map'


看看构建结果,没有了.map文件,而且构建出来的js文件也比source-map大了很多,因为inline-source-map把map文件打到js文件中了,最后一行把map文件inline进来了,最后一行很长很长


下面我们把动态设置多页面的代码添加到webpack.dev.js中,但是暂时先不设置devtool, 我们在search/index.js中加个断点

npm run dev :http://localhost:8080/search.html

这种很难调试
现在我们在webpack.dev.js中添加一句:devtool: source-map

npm run dev, http://localhost:8080/search.html,这种就非常好调试了,直接调试源代码。

我们把 source-map 换为 cheap-source-map,只能看到错误的行数

devtool: 'cheap-source-map'



六、提取页面公共资源

项目中有很多页面,这些页面使用的基础库一般都是一样的,有些页面可能还有一些公共的模块,打包的时候,如果把每个公共模块在每个页面都打一份,就很浪费,体积很大。

6.1 基础库分离splitChunksPlugin

splitChunksPlugin这个库是webpack4.x内置的,用于替换淘汰掉之前的CommonChunkPlugin
chunks参数说明:
1,async:异步引入的库才进行分离(动态import一个react库等等),同步引入的忽略(默认)
2,initial:同步引入的库进行分离,同步引入的库,如果有相同的,就进行分离,抽离出一个chunk出来
3,all:所有引入的库进行分离(推荐)
minChunks说明:
minChunks: 2 , 是指至少有2个地方用到了同一段代码,这一段代码就要被打包分离出一个chunk。专业点说就是设置最小引用次数。
maxInitalRequest说明:
maxInitalRequest: 3, 是指你通过插件分离出了4或者5个chunk,浏览器同时去请求的一个数量
minSize:分离的包提交大小。
下面通过实例,将react和react-dom公共资源包引进来,打包的时候,不用分析react和react-dom这种基础库。

6.2 操作实践

先看看html-webpack-externals-plugin的用法,配合cdn

npm i html-webpack-externals-plugin -D

webpack.prod.js添加代码:

const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')// plugins数组添加一项
new HtmlWebpackExternalsPlugin({externals: [{module: 'react',entry: 'https://now8.gtimg.com/now/lib/16.2.0/react.min.js',global: 'React'}, {module: 'react-dom',entry: 'https://now8.gtimg.com/now/lib/16.2.0/react-dom.min.js',global: 'ReactDOM'}]
})

附全部webpack.prod.js代码

const path = require('path')
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')// 动态设置多页面的函数
const setMPA = () => {const myEntry = {}const myHtmlWebpackPlugin = []const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'))// entryFiles =  [//   '/Users/guoyu/work/webpack-demo/src/index/index.js',//   '/Users/guoyu/work/webpack-demo/src/search/index.js'// ]Object.keys(entryFiles).forEach(index => {const entryFile = entryFiles[index]const match = entryFile.match(/src\/(.*)\/index\.js/)const pageName = match && match[1]myEntry[pageName] = entryFilemyHtmlWebpackPlugin.push(new HtmlWebpackPlugin({template: path.join(__dirname, `src/${pageName}/index.html`),filename: `${pageName}.html`,chunks: [pageName],inject: true,minify: {html5: true,collapseWhitespace: true,preserveLineBreaks: false,minifyCSS: true,minifyJS: true,removeComments: false}}))})return {entry: myEntry,htmlWebpackPlugin: myHtmlWebpackPlugin}
}
const { entry, htmlWebpackPlugin } = setMPA()module.exports = {entry: entry,output: {path: path.join(__dirname, 'dist'),filename: '[name]_[chunkhash:8].js'},mode: 'none',module: {rules: [{test: /\.js$/,use: 'babel-loader'},{test: /\.css$/,use: [// 'style-loader',MiniCssExtractPlugin.loader,'css-loader']},{test: /\.less$/,use: [// 'style-loader',MiniCssExtractPlugin.loader,'css-loader','less-loader',{loader: 'postcss-loader',options: {plugins: () => [require('autoprefixer')]}},{loader: 'px2rem-loader',options: {// 这里设置1个rem为75px,可以自己根据设备设置remUnit: 75,// 转换成rem后,保留8位小数remPrecision: 8}}]},{test: /\.(png|jpg|gif|jpeg)$/,use: [{loader: 'file-loader',options: {name: '[name]_[hash:8].[ext]'}}]},{test: /\.(woff|woff2|eot|ttf|otf)$/,use: [{loader: 'file-loader',options: {name: '[name]_[hash:8].[ext]'}}]}]},plugins: [new CleanWebpackPlugin(),new MiniCssExtractPlugin({filename: '[name]_[contenthash:8].css'}),new OptimizeCSSAssetsPlugin({assetNameRegExp: /\.css$/g,cssProcessor: require('cssnano')}),new HtmlWebpackExternalsPlugin({externals: [{module: 'react',entry: 'https://now8.gtimg.com/now/lib/16.2.0/react.min.js',global: 'React'}, {module: 'react-dom',entry: 'https://now8.gtimg.com/now/lib/16.2.0/react-dom.min.js',global: 'ReactDOM'}],files: ['search.html']})].concat(htmlWebpackPlugin),devtool: 'inline-source-map'
}

下面,我们看看如何用splitChunksPlugin,这是webpack4.x内置的,可以直接配置。先把上面的HtmlWebpackExternalsPlugin代码去掉,使用splitChunksPlugin来把react,react-dom分离成一个vendor。
下面是完整配置,只用关注第24行和122-128行

const path = require('path')
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')// 动态设置多页面的函数
const setMPA = () => {const myEntry = {}const myHtmlWebpackPlugin = []const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'))Object.keys(entryFiles).forEach(index => {const entryFile = entryFiles[index]const match = entryFile.match(/src\/(.*)\/index\.js/)const pageName = match && match[1]myEntry[pageName] = entryFilemyHtmlWebpackPlugin.push(new HtmlWebpackPlugin({template: path.join(__dirname, `src/${pageName}/index.html`),filename: `${pageName}.html`,chunks: ['gyVendors', pageName],//将splitChunks提取的vendors(gyVendors)加上inject: true,minify: {html5: true,collapseWhitespace: true,preserveLineBreaks: false,minifyCSS: true,minifyJS: true,removeComments: false}}))})return {entry: myEntry,htmlWebpackPlugin: myHtmlWebpackPlugin}
}
const { entry, htmlWebpackPlugin } = setMPA()module.exports = {entry: entry,output: {path: path.join(__dirname, 'dist'),filename: '[name]_[chunkhash:8].js'},mode: 'production',module: {rules: [{test: /\.js$/,use: 'babel-loader'},{test: /\.css$/,use: [// 'style-loader',MiniCssExtractPlugin.loader,'css-loader']},{test: /\.less$/,use: [// 'style-loader',MiniCssExtractPlugin.loader,'css-loader','less-loader',{loader: 'postcss-loader',options: {plugins: () => [require('autoprefixer')]}},{loader: 'px2rem-loader',options: {// 这里设置1个rem为75px,可以自己根据设备设置remUnit: 75,// 转换成rem后,保留8位小数remPrecision: 8}}]},{test: /\.(png|jpg|gif|jpeg)$/,use: [{loader: 'file-loader',options: {name: '[name]_[hash:8].[ext]'}}]},{test: /\.(woff|woff2|eot|ttf|otf)$/,use: [{loader: 'file-loader',options: {name: '[name]_[hash:8].[ext]'}}]}]},plugins: [new CleanWebpackPlugin(),new MiniCssExtractPlugin({filename: '[name]_[contenthash:8].css'}),new OptimizeCSSAssetsPlugin({assetNameRegExp: /\.css$/g,cssProcessor: require('cssnano')})].concat(htmlWebpackPlugin),// 配置splitChunks分离公共包,这是webpack4.x内置的一个插件optimization: {splitChunks: {cacheGroups: {commons: {test: /(rect|react-dom)/,name: 'gyVendors', // 记得在HtmlWebpackPlugin中把gyVendors加上chunks: 'all'}}}}
}



下面我们进一步探索 splitChunks的用法,我们手写一个公共函数,让search和index两个业务页面都用上这个公共函数commom/index.js

webpack.prod.js配置如下:

optimization: {splitChunks: {minSize: 0,//只要是公共包,就抽离出来cacheGroups: {commons: {name: 'commons', // 记得在HtmlWebpackPlugin中把commons加上chunks: 'all',minChunks: 2 // 公共包至少被引用了两次才会被抽离出来}}}
}

全配如下:

const path = require('path')
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')// 动态设置多页面的函数
const setMPA = () => {const myEntry = {}const myHtmlWebpackPlugin = []const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'))Object.keys(entryFiles).forEach(index => {const entryFile = entryFiles[index]const match = entryFile.match(/src\/(.*)\/index\.js/)const pageName = match && match[1]myEntry[pageName] = entryFilemyHtmlWebpackPlugin.push(new HtmlWebpackPlugin({template: path.join(__dirname, `src/${pageName}/index.html`),filename: `${pageName}.html`,chunks: ['commons', pageName], // 将splitChunks提取的commons加上inject: true,minify: {html5: true,collapseWhitespace: true,preserveLineBreaks: false,minifyCSS: true,minifyJS: true,removeComments: false}}))})return {entry: myEntry,htmlWebpackPlugin: myHtmlWebpackPlugin}
}
const { entry, htmlWebpackPlugin } = setMPA()module.exports = {entry: entry,output: {path: path.join(__dirname, 'dist'),filename: '[name]_[chunkhash:8].js'},mode: 'production',module: {rules: [{test: /\.js$/,use: 'babel-loader'},{test: /\.css$/,use: [// 'style-loader',MiniCssExtractPlugin.loader,'css-loader']},{test: /\.less$/,use: [// 'style-loader',MiniCssExtractPlugin.loader,'css-loader','less-loader',{loader: 'postcss-loader',options: {plugins: () => [require('autoprefixer')]}},{loader: 'px2rem-loader',options: {// 这里设置1个rem为75px,可以自己根据设备设置remUnit: 75,// 转换成rem后,保留8位小数remPrecision: 8}}]},{test: /\.(png|jpg|gif|jpeg)$/,use: [{loader: 'file-loader',options: {name: '[name]_[hash:8].[ext]'}}]},{test: /\.(woff|woff2|eot|ttf|otf)$/,use: [{loader: 'file-loader',options: {name: '[name]_[hash:8].[ext]'}}]}]},plugins: [new CleanWebpackPlugin(),new MiniCssExtractPlugin({filename: '[name]_[contenthash:8].css'}),new OptimizeCSSAssetsPlugin({assetNameRegExp: /\.css$/g,cssProcessor: require('cssnano')})].concat(htmlWebpackPlugin),// 配置splitChunks分离公共包,这是webpack4.x内置的一个插件optimization: {splitChunks: {minSize: 0,cacheGroups: {commons: {// test: /(rect|react-dom)/,name: 'commons', // 记得在HtmlWebpackPlugin中把commons加上chunks: 'all',minChunks: 2}}}}
}

npm run build之后




上面看到commons_d5edc0d8.js 180 bytes,而且是search和index两个地方用到:那么我们把

minSize: 0换成 minSize: 10000 再打包,你会发现commons_xxxxxxxx.js不见了,因为不满足min配置



同样

minChunks: 2换成 minChunks: 3 再打包,你会发现commons_xxxxxxxx.js不见了,因为不满足min配置

6.3 结合webpack-bundle-analyzer分析

npm install webpack-bundle-analyzer -D
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
// plugins里添加如下的插件
new BundleAnalyzerPlugin()

package.json添加如下:

  "scripts": {"build": "webpack --config webpack.prod.js","watch": "webpack --watch","dev": "webpack-dev-server --config webpack.dev.js --open",// 下面一句才是重点"analyz": "analyz=true npm run build"}

直接看代码

optimization: {splitChunks: {minSize: 0,cacheGroups: {vendors: {test: /(react|react-dom)/,name: 'vendors',chunks: 'all',priority: -10},commons: {name: 'commons',chunks: 'all',minChunks: 2,priority: -20},ruok: {test: /[\\/]node_modules[\\/]/,name: 'nModules',chunks: 'all',priority: -30},default: {name: 'guoyu-default',chunks: 'all',minChunks: 2,priority: -40}}}
}

我们看看上面的webpack配置代码,我们源码中的search/index.js和index/index.js都引用了common/index.js。
这种情况既满足上面webpack配置中的commons也满足default。

那么既然common/index.js既满足commons又满足default,那么common/index.js到底会被打包到哪个chunk呢?答案就是 priority,priority谁的大,就打包到谁里面。上面,commons的priority大。我们看看结果。

我们把default的priority改为-10,大于commons的-20,再次分析

6.4 问答

Q1:如果我既想提取静态资源react,又想提取公共资源还想打到不同的文件里怎么办,支持数组方式吗?
A1:可以的,这个cacheGroup 可以配置多个组的。想打包到不同的文件只需要传到 html-webpack-plugin的 chunk 按照需要设置即可

Q2:同时分离基础库和公共脚本,具体要怎么配呢?期望提取出的common公共脚本 + vendor基础库,都可以自定义名称。但是貌似不行。。打包出来的vendor,总是vendors~xxx_hash.js这样的。而且设置两次name,第二次的会覆盖掉第一次的,配置如下:

cacheGroups: {commons: {name: 'common',chunks: 'all',minChunks: 2},vendors: {test: /(react|react-dom)/,name: 'vendor',chunks: 'all'}
}
A2:正确配置如下:
optimization: {splitChunks: {minSize: 0,cacheGroups: {vendors: {test: /(react|react-dom)/,name: 'vendors',chunks: 'all',priority: -10},commons: {name: 'commons',chunks: 'all',minChunks: 2,priority: -20}}}
}

Q3:priority 是什么意思?
A3:优先级权重,使用一个数字表示,数字越大,表示优先级越高,通俗的说,就是如果有一个模块满足了多个缓存组的条件就会去按照权重划分,谁的权重高就优先按照谁的规则处理。比如,一个模块既满足上面A2中vendors的分组要求,也满足commons的分组要求,谁的权重大,就打包到谁的分组。

七、摇树优化TreeShaking

7.1 概念

一个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到bundle里,tree shaking就是只把用到的方法打入bundle,没用到的方法在uglify阶段被擦除掉。
tree shaking在webpack4.x默认支持,mode设置为production的情况默认开启
但是,使用treeshaking 必须是ES6语法,cjs语法不支持。
DEC(Dead Code Elimination)
DEC:擦除淘汰无用代码

1,代码不会被执行,不可到达

if (false) {console.log('......这段代码永远不会被执行......')
}

2,代码执行的结果不会被用到
3,代码只会影响死变量(只写不读)

7.2 treeShaking的原理

主要是利用了ES6模块的特点:

  • 只能作为模块顶层的语句出现(import、export语句都在文件顶层出现)
  • import 的模块只能是字符串常量
  • import binding 是immutable的(import一个模块后,不能被修改)
    代码擦除:uglify阶段删除无用代码
    所以,commonJS是不具备上面三个特点的,因为可以动态require,不同条件require不同的模块,这都是cjs写法。为什么?treeshaking最本质的还是对模块代码的静态分析,在编译阶段,哪些代码会被用到都是要确定下来的,不是说在代码运行的时候再去分析哪些代码有没有用到,不能这么操作。哪些代码没用到,treeshaking会将这些代码标识出来,uglify阶段删除无用代码。

7.3 treeShaking实际操作

1,我们在search文件夹下,创建一个search/tree-shaking.js文件内容如下:

export function a() {return 'this is func a'
}export function b() {return 'this is func b'
}
2,在search/index.js中只引入a方法
import { a } from './tree-shaking'

3,webpack.prod.js将mode换成’none’,因为之前的’production’默认开启了treeShaking

mode: 'none'




4,执行npm run build

从上图可以看到,打包出来的,没有开启treeShaking,这两个代码都打包进去了,尽管我们都没用到。

5,下面,我们none换为 production

可见,根本没打进去,无用代码被TreeShaking掉了。
那我们实际用一下a,在search/index.js中做如下改动

编译打包之后


用的着的留下了,用不着的摇走了。
再看一个例子

npm run build


都没被打进包里。

八、Scope Hoisting使用和原理分离

8.1 背景

构建后的代码存在大量的闭包代码
比如,新建两个common/a.js和common/b.js两个文件。在src/index/index.js中引用
a.js和b.js分别是

export function testA() { console.log('this is testA..........')
}
export function testB() { console.log('this is testB..........')
}

把webpack.dev.js中的mode换为none,不然是压缩混淆代码,看不清

 mode: 'none'



一个项目,可能有几百上千个包裹。
1,大量函数闭包包裹代码,导致体积增大,模块越多越明显。
2,都是通过闭包方式,运行代码时候,创建的函数作用域变多,内存开销变大。
下面我们看看打包模块

/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "testA", function() { return testA; });
function testA() {console.log('this is testA..........');
}/***/ }),
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "testB", function() { return testB; });
function testB() {console.log('this is testB..........');
}/***/ })

模块初始化函数:就是webpack对模块进行处理,增加的包裹
我们可以看到

  • 被import进来的,被webpack转换后的模块都会带上一层包裹
  • import会被转换为 webpack_require

进一步分析一下:

/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {/******/        if(!__webpack_require__.o(exports, name)) {/******/            Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/        }
/******/    };
/******/
/******/    // define __esModule on exports
/******/    __webpack_require__.r = function(exports) {/******/        if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {/******/            Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/        }
/******/        Object.defineProperty(exports, '__esModule', { value: true });
/******/    };
/******/
/******/    // create a fake namespace object
/******/    // mode & 1: value is a module id, require it
/******/    // mode & 2: merge all properties of value into the ns
/******/    // mode & 4: return value when already ns object
/******/    // mode & 8|1: behave like require
/******/    __webpack_require__.t = function(value, mode) {/******/        if(mode & 1) value = __webpack_require__(value);
/******/        if(mode & 8) return value;
/******/        if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/        var ns = Object.create(null);
/******/        __webpack_require__.r(ns);
/******/        Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/        if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/        return ns;
/******/    };
/******/
/******/    // getDefaultExport function for compatibility with non-harmony modules
/******/    __webpack_require__.n = function(module) {/******/        var getter = module && module.__esModule ?
/******/            function getDefault() { return module['default']; } :
/******/            function getModuleExports() { return module; };
/******/        __webpack_require__.d(getter, 'a', getter);
/******/        return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _common_a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
/* harmony import */ var _common_b__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
// import _ from 'lodash'
// import { commonTest } from '../../common'
// function component() {//   commonTest('Index')
//   let ele = document.createElement('div')
//   ele.innerHTML = _.join(['Hello', 'guoyu'], ' ')
//   return ele
// }
// document.body.appendChild(component())Object(_common_a__WEBPACK_IMPORTED_MODULE_0__["testA"])();
Object(_common_b__WEBPACK_IMPORTED_MODULE_1__["testB"])();/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "testA", function() { return testA; });
function testA() {console.log('this is testA..........');
}/***/ }),
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "testB", function() { return testB; });
function testB() {console.log('this is testB..........');
}/***/ })
/******/ ]);

从上面打包出来的文件可以看出:

  • 打包出来的是个匿名闭包
  • modules是一个数组,每一项都是一个模块初始化函数
  • __webpack_require__用来加载模块,返回module.exports
  • 通过webpack_require_method(0)启动程序

分析一个函数:

function __webpack_require__(moduleId) {// Check if module is in cacheif(installedModules[moduleId]) {return installedModules[moduleId].exports;}// Create a new module (and put it into the cache)var module = installedModules[moduleId] = {i: moduleId,l: false,exports: {}};// Execute the module function// require另一个模块(依赖),会传递如下参数modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);// Flag the module as loadedmodule.l = true;// Return the exports of the modulereturn module.exports;
}

8.2 原理

在上面了解了webpack模块机制之后,我们看看有么有办法对前面提到的包裹代码有所优化?
scope hoisting和treeShaking一样,都是从rollup中借鉴过来的。
将所有模块的代码按照引用顺序放在一个函数作用域里面,然后适当的重命名一些变量,以防止变量名冲突。
通过scop hoisting 可以减少函数声明代码和内存开销
scope hoisting 是webpack3提出的,翻译过来就是“作用域提升”,webpack会把js文件提升到引入者的顶部。
在之前,如果a模块引用了b模块,那么我们可以把a模块放在b模块前面,a调用b,但他们之间有模块包裹,所以并不影响,如果要消除这种包裹代码的话,我们根据模块的引用顺序,进行位置排放,也就是我们要把被引用的b模块放在a模块前面,那么a就可以直接读取b的内容,这样就消除了包裹,减少了内存开销和函数申明代码。

在webpack4.x中,scope hoisting 在mode为production时默认开启。
但是在webpack3.x中还是要手动配置的。下面是webpack3.x中的配置:

// webpack3.x
plugins: [new webpack.optimize.ModuleConcatenationPlugin() // 开启scope hoisting
]

在webpack4.x中,scope hoisting 在mode为production时默认开启。
必须是ES6语法,cjs不支持,因为scope hoisting是一个静态分析过程,cjs是一个动态引入模块的方式,没办法进行静态分析,比如分析模块的引入顺序。

8.3 使用

首先,禁止掉production,把mode设置为none,看看打包结果。
npm run build 看看。我们回到 8.1 贴出来的打包后的代码,我们没有开启scope hoisting的时候,可以看到如果模块多,包裹数量也会越来越多。坏处显而易见。
接下来,我们使用scope hoisting功能,看看打包后的效果。但是,我们不能草率的把mode设置为production,因为这样会uglify代码,都是压缩混淆的,看的不直观。
所以,我们依旧保持 mode 为 ‘none’,但是按照webpack3.x的方式,手动在plugins中加入插件。

const webpack = require('webpack')mode: 'none',plugins: [new webpack.optimize.ModuleConcatenationPlugin() // 开启scope hoisting
]

打包之后的代码

/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {/******/        if(!__webpack_require__.o(exports, name)) {/******/            Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/        }
/******/    };
/******/
/******/    // define __esModule on exports
/******/    __webpack_require__.r = function(exports) {/******/        if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {/******/            Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/        }
/******/        Object.defineProperty(exports, '__esModule', { value: true });
/******/    };
/******/
/******/    // create a fake namespace object
/******/    // mode & 1: value is a module id, require it
/******/    // mode & 2: merge all properties of value into the ns
/******/    // mode & 4: return value when already ns object
/******/    // mode & 8|1: behave like require
/******/    __webpack_require__.t = function(value, mode) {/******/        if(mode & 1) value = __webpack_require__(value);
/******/        if(mode & 8) return value;
/******/        if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/        var ns = Object.create(null);
/******/        __webpack_require__.r(ns);
/******/        Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/        if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/        return ns;
/******/    };
/******/
/******/    // getDefaultExport function for compatibility with non-harmony modules
/******/    __webpack_require__.n = function(module) {/******/        var getter = module && module.__esModule ?
/******/            function getDefault() { return module['default']; } :
/******/            function getModuleExports() { return module; };
/******/        __webpack_require__.d(getter, 'a', getter);
/******/        return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = 12);
/******/ })
/************************************************************************/
/******/ ({/***/ 12:
/***/ (function(module, __webpack_exports__, __webpack_require__) {"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);// CONCATENATED MODULE: ./common/a.js
function testA() {console.log('this is testA..........');
}
// CONCATENATED MODULE: ./common/b.js
function testB() {console.log('this is testB..........');
}testA();
testB();/***/ })/******/ });

九、代码分割和动态import

9.1 代码分割的意义

对于大的Web应用来说,将所有的代码都放在同一个文件中显然是不够有效的,比如只打包出一个大大的bundle.js显然是不合理的,特别是当你的某些代码是在某些特定的时候才会被用到。
webpack有一个功能就是将你的代码分割成chunks(语块),当代码运行到需要它们的时候再进行加载。
适用场景:

  1. 抽离相同代码到一个共享块
  2. 脚本懒加载,使得初始下载的代码更小。

9.2 懒加载JS脚本

ES6:动态import,目前没得到原生支持,需要babel转换。按需加载,不是让你一开始就把所有模块都加载出来。

npm install @babel/plugin-syntax-dynamic-import --save-dev

打包之后,会把一些包单独打出来,比如本来打包出来的search.js只有这么一个文件,但你你在里面动态加载了一些模块,那么打包之后,就会多出一些文件,也就是会额外的分割一些文件,其实就是从原来的search.js文件里分离出来的。你按了一个按钮,切换一个tab,需要用到某个模块,加载的时机通过你的业务代码去控制,加载到某个模块后就会去请求对应的js,然后去异步的加载这个js,这就是动态import加载的实际效果。
动态import实际举例:
我们在search文件夹下增加search/text.js文件,也就是一个Text组件.

npm install @babel/plugin-syntax-dynamic-import --save-dev

.bablerc文件:

{"plugins": ["@babel/plugin-syntax-dynamic-import"]
}

打包之后,会把一些包单独打出来,比如本来打包出来的search.js只有这么一个文件,但你你在里面动态加载了一些模块,那么打包之后,就会多出一些文件,也就是会额外的分割一些文件,其实就是从原来的search.js文件里分离出来的。你按了一个按钮,切换一个tab,需要用到某个模块,加载的时机通过你的业务代码去控制,加载到某个模块后就会去请求对应的js,然后去异步的加载这个js,这就是动态import加载的实际效果。
动态import实际举例:
我们在search文件夹下增加search/text.js文件,也就是一个Text组件.

import React from 'react'
export default() => <div>动态引入的import</div>


在search/index.js里面 动态引入这个Text组件。当点击的时候,动态加载这个Text组件。

npm run build之后

我们看看需要动态加载的这个文件的内容:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[3],{/***/ 12:
/***/ (function(module, __webpack_exports__, __webpack_require__) {"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);/* harmony default export */ __webpack_exports__["default"] = (function () {return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, "\u52A8\u6001\u5F15\u5165\u7684import");
});/***/ })}]);

看到 webpackJsonp 了吗?
点击按钮,加载这个模块的时候,会发起一个jsonp的请求来加载这个模块。
不信,打开Chrome控制台的network。当我们点击,调用加载函数的时候,会出现一个js的请求。


如上图:点击后,懒加载了一个脚本。

webpack:进阶用法(一)相关推荐

  1. (数据科学学习手札61)xpath进阶用法

    一.简介 xpath作为对网页.对xml文件进行定位的工具,速度快,语法简洁明了,在网络爬虫解析内容的过程中起到很大的作用,除了xpath的基础用法之外(可参考我之前写的(数据科学学习手札50)基于P ...

  2. Android属性动画进阶用法

    2019独角兽企业重金招聘Python工程师标准>>> 在上周二文章中介绍补间动画缺点的时候有提到过,补间动画是只能对View对象进行动画操作的.而属性动画就不再受这个限制,它可以对 ...

  3. canvas图形处理和进阶用法

    前面的话 上一篇博客介绍了canvas基础用法,本文将更进一步,介绍canvas的图形处理和进阶用法 图形变换 图形变换是指用数学方法调整所绘形状的物理属性,其实质是坐标变形.所有的变换都依赖于后台的 ...

  4. Frida Hook Android App 进阶用法之 Java 运行时

    FridaHookAndroid 本文旨在覆盖使用 Frida 对 Android App 进行 hook 的绝大多数场景.文章提到的所有代码以及被测 App,详见:https://github.co ...

  5. Web scraper使用教程-进阶用法(二)-爬取二级页面内容

    进阶用法(二)-爬取二级页面内容 1. 爬取网址 https://docs.microsoft.com/en-us/officeupdates/update-history-microsoft365- ...

  6. pandas进阶用法(一)筛选条件、多重索引、缺失值

    一篇比较好的pandas指南,适合已经熟悉pandas,并想掌握一些进阶用法的读者,不适合对pandas完全不了解的新人.文章大部分是Stack Overflow常见问题集合. pandas 官网 原 ...

  7. Python Requests库进阶用法——timeouts, retries, hooks

    Python HTTP 请求库在所有编程语言中是比较实用的程序.它简单.直观且在 Python 社区中无处不在. 大多数与 HTTP 接口程序使用标准库中的request或 urllib3. 由于简单 ...

  8. goto在Java中的替代(break和continue的进阶用法)

    文章目录 前言 一.goto是什么? 二.goto与Java的关系 1.关系 2.Java中的标签 2.break和continue的进阶用法 总结 前言 goto语句是在源码级上的跳转,这使得其招致 ...

  9. babel进阶用法之处理json文件

    前言 社区里面关于babel的介绍非常多了,这里不想重复这些常见内容.很多人认为babel只是一个语法转译工具,将浏览器无法识别的高级语法进行转换(polyfill),提升开发体验.其实babel是一 ...

最新文章

  1. ipad无法与itunes同步,提示因为这台电脑不再被授权使用在此ipad上购买的项目解决方案...
  2. css根据文字长度实现宽度自适应
  3. c语言中go的作用,go语言与c语言的相互调用
  4. mysql最大述_mysql最大字段数量及 varchar类型总结
  5. PAT_B_1004_Java(20分)
  6. 一位台湾校长的讲话。学习!!!
  7. 【Python基础入门系列】第09天:Python tuple
  8. linux下使用John检测用户是否存在弱口令
  9. TCP/IP之封装,分用,server模型
  10. Live2D在Unity中的使用
  11. labuladong算法小抄pdf下载
  12. navicat的使用技巧
  13. 大数据分析方法有哪几种?
  14. php unpack 详解,【PHP】 pack unpack 详解
  15. Luminar 4:AI 人像照片增强器
  16. 数字孪生开启传统行业数字化转型升级之路
  17. 注塑机计算机控制器,注塑机微机控制器,Microprocessor-based Controller for PIM,音标,读音,翻译,英文例句,英语词典...
  18. Arun Gupta通过将HTML5与Java EE 7拥抱来提高生产力
  19. DBPwAudit -数据库密码破解工具的使用
  20. 日常英语单词 - 食物

热门文章

  1. The Inventor Mentor-第十二章 传感器
  2. 【集成学习】boosting和bagging
  3. 【GlobalMapper精品教程】032:浏览地理照片及航线信息(航测应用)
  4. 软件测试工具大全——全而精
  5. Unity Shader - Custom DirectionalLight ShadowMap 自定义方向光的ShadowMap
  6. windows批量删除当前目录下的空文件夹
  7. 关于EasyNVR拉流摄像头的视频流存在视频流锁定机制的问题说明
  8. np.digitize()函数
  9. 浅谈ERP运维在企业信息化建设中的作用
  10. python可以制作游戏脚本吗_如何用python制作游戏脚本?