这篇主要介绍《webpack优化环境配置(下)》。(demo代码github地址)
知识点包括:
一、懒加载和预加载
懒加载
懒加载就是,在实际项目中,某个.js文件,还没有用到,此时不进行加载,当网页中进行某个功能,有需要时在加载。
1、复制代码分割工程文件,修改其中的webpack.config.js,进行精简。
const { resolve } = require('path'); const Htmlwebpackplugin = require('html-webpack-plugin'); module.exports = { entry: './src/js/index.js', output: { filename: 'js/[name].[contenthash:10].js', path: resolve(__dirname, 'build') }, plugins: [ new Htmlwebpackplugin({ template: './src/index.html', minify: { collapseWhitespace: true, removeComments: true } }), ], optimization: { splitChunks: { chunks: 'all' } }, mode: 'production' }
2、然后修改index.js文件
console.log('index.js文件被加载了'); // 给首页的按钮增加一个点击事件,为了实现懒加载,即用到某个js文件时才加载该文件 // 引入方式改为动态引入 document.getElementById('btn').onclick = function () { import('./test').then(({ mul }) => { console.log(mul(4, 5)); }); }
3、修改test.js代码
console.log('test.js文件被加载了'); export function mul(x, y) { return x * y; } export function count(x, y) { return x - y; }
4、修改index.html代码,增加一个按钮,当点击该按钮时,test.js中的功能被需要,然后被加载。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>懒加载</h1> <button id="btn">按钮</button> </body> </html>
5、然后终端运行npm run build
,打开打包后的index.html。
发现,点击按钮后,test.js文件才被加载。
预加载
打开网页的时候,所有的js文件都加载了,缓存到内存里,然后网页中某个功能实现需要js文件时,直接从内存中读取。
1、修改index.js代码,增加webpackPrefetch: true
。
console.log('index.js文件被加载了'); // 给首页的按钮增加一个点击事件,为了实现懒加载,即用到某个js文件时才加载该文件 // 引入方式改为动态引入 document.getElementById('btn').onclick = function () { // webpackPrefetch: true开启预加载 import(/*webpackChunkName:'test',webpackPrefetch: true*/'./test').then(({ mul }) => { console.log(mul(4, 5)); }); }
2、然后输入npm run build
重新打包。
打开生成的index.html,可以看到,网页一打开,全部被加载了,点击按钮后,test.js文件开始被调用。
总结
- 懒加载:当文件需要使用时才加载~
- 预加载prefetch:会在使用之前,提前加载js文件
- 正常常加载可以认为是并行加载(同一时间加载多个文件)
- 预加载prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
二、PWA(离线可访问)
渐进式网络应用程序(progressive web application - PWA),是一种可以提供类似于native app(原生应用程序) 体验的 web app(网络应用程序)。
1、复制tree shaking工程文件。
2、实现该功能需要一个插件,输入npm i workbox-webpack-plugin -D
下载。然后在webpack.config.js中使用
const { resolve } = require('path'); const minicssextractplugin = require('mini-css-extract-plugin'); process.env.NODE_ENV = 'production' const cssminimizerwebpackplugin = require('css-minimizer-webpack-plugin'); const Htmlwebpackplugin = require('html-webpack-plugin'); const workboxwebpackplugin = require('workbox-webpack-plugin') // PWA:渐进式网络开发应用程序(离线可访问) // 通过一个插件workbox-webpack-plugin module.exports = { entry: './src/js/index.js', output: { filename: 'js/built.[contenthash:10].js', path: resolve(__dirname, 'build') }, module: { rules: [{ oneOf: [ { test: /\.css$/, use: [ minicssextractplugin.loader, 'css-loader', { loader: 'postcss-loader', options: { postcssOptions: { plugins: [require('postcss-preset-env')()] } } } ] }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: { version: 3 }, targets: { chrome: '60', firefox: '50' } } ] ], cacheDirectory: true, } }, { test: /\.(jpg|png|gif)$/, loader: 'url-loader', options: { limit: 8 * 1024, outputPath: 'imgs', esModule: false }, type: 'javascript/auto' }, { test: /\.html$/, loader: 'html-loader', options: { esModule: false, } }, { exclude: /\.(js|css|less|html|jpg|png|gif)$/, loader: 'file-loader', options: { outputPath: 'media', esModule: false, }, type: 'javascript/auto' } ] } ] }, plugins: [ new minicssextractplugin({ filename: 'css/built.[contenthash:10].css' }), new cssminimizerwebpackplugin( ), new Htmlwebpackplugin({ template: './src/index.html', minify: { collapseWhitespace: true, removeComments: true } }), // 使用PWA new workboxwebpackplugin.GenerateSW({ // 进行两个设置,分别: // 1.帮助serviceworker快速启动 //2.删除旧的serviceworkerl // 最后生成一个serviceworker配置文件 clientsClaim: true, skipWaiting: true }) ], mode: 'production' }
3、然后输入npm run build
进行打包。打包后看到生成两个.js文件
生成的service-worker代码必须运行在服务器上,有三种方法,一是通过nodejs编写代码,二是输入npm install http-server --save-dev安装一个包,还要修改 package.json 的 scripts 部分,增加"start": "http-server dist",然后输入npm start 启动服务器,将build目录下所有资源作为静态资源暴露出去。第三种方法是输入npm install -D webpack-dev-server,然后npx webpack serve。最后点击访问生成的网址。
【!!!注:我在测试后发现,第二种生成的路径打不开,第三种执行后报了错。不知道什么原因~~~下面是原博主的测试执行结果】
把网络设置为离线,看是否还能访问。
访问正常。
三、多进程打包
1、复制上一小节工程文件。
同一时间多个进程同时打包,优化打包时间。
2、需要下载一个loader。终端输入命令npm i thread-loader -D
,修改config.js代码。
const { resolve } = require('path'); const minicssextractplugin = require('mini-css-extract-plugin'); process.env.NODE_ENV = 'production' const cssminimizerwebpackplugin = require('css-minimizer-webpack-plugin'); const Htmlwebpackplugin = require('html-webpack-plugin'); const workboxwebpackplugin = require('workbox-webpack-plugin') module.exports = { entry: './src/js/index.js', output: { filename: 'js/built.[contenthash:10].js', path: resolve(__dirname, 'build') }, module: { rules: [{ oneOf: [ { test: /\.css$/, use: [ minicssextractplugin.loader, 'css-loader', { loader: 'postcss-loader', options: { postcssOptions: { plugins: [require('postcss-preset-env')()] } } } ] }, { test: /\.js$/, exclude: /node_modules/, use: [ // 开启多进程打包,进程启动大概为600ms,进程通信也有开销。只有工作消耗时间比较长,才需要 // 一般与babel loader结合使用 'thread-loader', { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: { version: 3 }, targets: { chrome: '60', firefox: '50' } } ] ], cacheDirectory: true, } } ] }, { test: /\.(jpg|png|gif)$/, loader: 'url-loader', options: { limit: 8 * 1024, outputPath: 'imgs', esModule: false }, type: 'javascript/auto' }, { test: /\.html$/, loader: 'html-loader', options: { esModule: false, } }, { exclude: /\.(js|css|less|html|jpg|png|gif)$/, loader: 'file-loader', options: { outputPath: 'media', esModule: false, }, type: 'javascript/auto' } ] } ] }, plugins: [ new minicssextractplugin({ filename: 'css/built.[contenthash:10].css' }), new cssminimizerwebpackplugin( ), new Htmlwebpackplugin({ template: './src/index.html', minify: { collapseWhitespace: true, removeComments: true } }), // 使用PWA new workboxwebpackplugin.GenerateSW({ // 进行两个设置,分别: // 1.帮助serviceworker快速启动 //2.删除旧的serviceworkerl // 最后生成一个serviceworker配置文件 clientsClaim: true, skipWaiting: true }) ], mode: 'production' }
3、终端输入npm run build
进行打包,一般当项目文件比较大时,这个功能的优势才会更明显。
四、externals
externals是防止将某些 import 的包(package)打包到 build(存放打包后文件的地方)中,是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
例如,从 CDN 引入 jQuery,而不是把它打包。
1、复制打包html资源工程,并重命名。复制好的工程文件目录如下
2、修改webpack.config.js代码
const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: './src/index.js', output: { filename: 'built.js', path: resolve(__dirname, 'build') }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ], mode: 'production', // 外部扩展(externals) // 防止将某些 import 的包(package)打包到 built 中, externals: { jquery: 'jQuery' } }
3、修改index.js代码,使用jquery
import $ from 'jquery'; console.log($); function add(x, y) { return x + y; } console.log(add(1, 2));
4、然后输入npm run build
我们发现生成的built.js文件大小是312bytes。
如果把jquery也打包的话,文件大小肯定远远大于这个值。
5、最后记得要在index.html中手动引入jquery。
因为我们没有打包jquery,被externals设置排除了,手动引入后,才能正常使用。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1 id="title">hello html</h1> <script src="https://code.jquery.com/jquery-3.1.0.js" integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk=" crossorigin="anonymous"> </script> </body> </html>
6、再重新打包一次。在浏览器打开生成的index.html文件。可以看到此时的built.js文件是312bytes,网页的功能也正常。
五、DLL(动态链接库)
1、复制打包html资源工程。并重命名。
DLL功能就是:单独打包,把不同的文件最后打包到不同的文件,即多对多的关系。
2、在复制的工程文件夹下新增webpack.dll.js文件。其代码如下
/* 使用dll技术,对某些库(第三方库:jquery、react、vue. . . )进行单独打包 当你运行webpack时,默认查找webpack.config.js配置文件 而我们需要运行webpack.dll.js文件 所以输入命令: webpack --config webpack.dll.js,进行修改 */ const { resolve } = require('path'); // webpack自带的插件 const webpack = require('webpack') module.exports = { entry: { //最终打包生成的[name] --> jquery // ['jquery']-- > 要打包的库是jquery jquery: ['jquery'] }, output: { filename: '[name].js', path: resolve(__dirname, 'dll'), library: '[name]_[hash:10]'// 打包的库里面向外暴露出去的内容叫什么名字 }, plugins: [ // 使用webpack自带的插件,打包生成一个manifest.json文件,提供和jquery的映射 new webpack.DllPlugin({ name: '[name]_[hash:10]',//映射库的暴露的内容名称 path: resolve(__dirname, 'dll/manifest.json')//输出文件路径 }) ], mode: 'production' }
3、终端输入npm i jquery --save
下载jquery包。然后修改package.json中代码
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "webpack --mode development", "build": "webpack --mode production", "dill":"webpack --config webpack.dll.js" },
4、然后终端输入:npm run dill
。这样就修改了打包时默认的配置文件,变成了webpack.dll.js。
5、至此, 我们已经把jquery单独打包出来了到一个文件夹中,那么以后再打包时,就可以不用在打包jquery了。
想打包其他非官方modules时,需要再修改webpack.config.js代码。
/*工作流程 loader: 1下载 2使用(配置loader) plugins: 1.下载 2.引入 3使用 */ const { resolve } = require('path'); // 引入插件 const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack') module.exports = { entry: './src/index.js', output: { filename: 'built.js', path: resolve(__dirname, 'build') }, module: { rules: [ // loader的配置 ] }, plugins: [ //plugins的配置 // html-webpack-plugin配置 // 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(S/cSs) new HtmlWebpackPlugin({ //复制'./src/index.html’文件,并自动引入打包输出的所有资源(JS/cSs) template: './src/index.html' }), // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~ new webpack.DllReferencePlugin({ manifest: resolve(__dirname, 'dll/manifest.json') }) ], mode: 'development' }
6、然后我们在index.js引入jquery代码。
import $ from 'jquery' console.log($); function add(x, y) { return x + y; } console.log(add(1, 2));
如果此时不修改config.js中代码,直接进行生产环境下的打包,npm run build,则最后的打包文件还是会把jquery与自己写的代码杂糅起来。
7、使用了webpack.DllReferencePlugin插件后,输入npm run build,查看效果。
此时的built.js中没有柔和jquery代码,体积很小。
那么我们需要用jquery,该怎么办呢?
8、此时需要另一个插件,输入npm i add-asset-html-webpack-plugin -D
.
该插件将某个文件打包输出去,并在html中自动引入该资源。
然后在config.js中使用。
/*工作流程 loader: 1下载 2使用(配置loader) plugins: 1.下载 2.引入 3使用 */ const { resolve } = require('path'); // 引入插件 const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin') module.exports = { entry: './src/index.js', output: { filename: 'built.js', path: resolve(__dirname, 'build') }, module: { rules: [ // loader的配置 ] }, plugins: [ //plugins的配置 // html-webpack-plugin配置 // 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(S/cSs) new HtmlWebpackPlugin({ //复制'./src/index.html’文件,并自动引入打包输出的所有资源(JS/cSs) template: './src/index.html' }), // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~ new webpack.DllReferencePlugin({ manifest: resolve(__dirname, 'dll/manifest.json') }), // 将某个文件打包输出去,并在html中自动引入该资源 new AddAssetHtmlWebpackPlugin({ filepath: resolve(__dirname, 'dll/jquery.js'), outputPath: "auto" }) ], mode: 'development' }
9、此时在重新打包一次,npm run build
。
此时在运行打包后的html文件就没问题了。
总结
我们通过一个webpack.dll.js先单独打包jquery文件,然后在webpack.config.js中使用了插件webpack.DllReferencePlugin,告诉webpack,在生产环境打包时,不需要再对jquery打包了,然后又使用了插件AddAssetHtmlWebpackPlugin,告诉webpack,将之前单独打包的jquery自动输出并引入到html文件中去。就可以避免在修改配置后再打包时,还会重复打包jquery,节省了时间。
注:笔记转载自疯子的梦想@博客,课程来自尚硅谷b站Webpack5实战课程