Webpack5,了解从0到1搭建一个项目的细节 |
您所在的位置:网站首页 › 0a1d63485f9 › Webpack5,了解从0到1搭建一个项目的细节 |
「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」 前言本篇文章不只是搭建webpack5的项目,还有个更重要的目的是,为了在搭建过程中了解每个插件,loader的作用,为什么要使用这些东西,这些东西带来了什么。 我为本项目写了一个cli工具:fight-react-cli,想直接看最终结果的同学,安装一下这个cli工具,初始化一个项目就能查看全部的配置。 准备工作首先我们创建一个项目webpack-demo,然后初始化npm,然后在本地安装webpack和webpack-cli: mkdir webpack-demo cd webpack-demo npm init -y npm install webpack webpack-cli --save-dev安装的webpack包则是webpack的核心功能包,webpack-cli则是webpack的命令行工具,可以在终端中使用webpack命令启动项目和打包项目。 然后我们在项目的根目录下创建一个文件夹webpack,在这个文件夹中创建三个文件用以区分环境: webpack.common.js // 公用配置 webpack.dev.js // 开发时的配置 webpack.prod.js // 打包构建时的配置然后在根目录创建src文件夹,在src文件夹下面创建index.js: // src/index.js const el = document.getElementById('root'); el.innerHTML = 'hello webpack5'; 基本配置我们在webpack文件夹下的webpack.common.js中来写基本的配置: // webpack/webpack.common.js const path = require('path'); module.exports = (webpackEnv) => { const isEnvDevelopment = webpackEnv === 'development'; const isEnvProduction = webpackEnv === 'production'; return { mode: webpackEnv, entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [] }, plugins: [], }; };这里我们导出了一个函数,函数中返回了webpack的配置信息。当这个函数被调用时,会传入当前运行的环境标识webpackEnv,它的值是development或者production,并将webpackEnv赋值给了mode,用于根据不同模式开启相应的内置优化,还有个作用则是根据不同环境自定义开启不同的配置,在后续配置中会用到。 在配置信息中是webpack的5大基本模块: mode:模式,通过选择:development,production,none这三个参数来告诉webpack使用相应模式的内置优化。 entry:设置入口文件。 output:告诉wenpack打包出的文件存放在哪里 module.rules:loader(加载器),webpack本身只支持处理js,json文件,要想能够处理其它类型的文件,如:css,jsx,ts,vue等,则需要相应的loader将这些文件转换成有效的模块。 plugins:插件,loader用于处理不支持的类型的文件,而plugin则可以用于执行范围更广的任务,如:压缩代码(new TerserWebpackPlugin()),资源管理(new HtmlWebPackPlugin()),注入环境变量(new webpack.DefinePlugin({...}))等。 配置webpack-dev-server基本配置完成了,我们现在想要让代码运行起来,并且当代码修改后可以自动刷新页面。 首先先安装webpack-dev-server: npm install --save-dev webpack-dev-server安装完成后,我们进入webpack.dev.js中来添加开发时的配置: const webpackCommonConfig = require('./webpack.common.js')('development'); module.exports = { devServer: { host: 'localhost', // 指定host,,改为0.0.0.0可以被外部访问 port: 8081, // 指定端口号 open: true, // 服务启动后自动打开默认浏览器 historyApiFallback: true, // 当找不到页面时,会返回index.html hot: true, // 启用模块热替换HMR,在修改模块时不会重新加载整个页面,只会更新改变的内容 compress: true, // 启动GZip压缩 https: false, // 是否启用https协议 proxy: { // 启用请求代理,可以解决前端跨域请求的问题 '/api': 'www.baidu.com', }, }, ...webpackCommonConfig, };在这里我们首先引入了webpack.common.js,上面我们介绍了这个文件导出一个函数,接收环境标识作为参数,这里我们传入的是development,然后将返回的配置对象webpackCommonConfig,与开发时的配置进行了合并。 配置html-webpack-pluginhtml-webpack-plugin的作用是生成一个html文件,并且会将webpack构建好的文件自动引用。 npm install --save-dev html-webpack-plugin安装完成后,在webpack.common.js中添加该插件: // webpack/webpack.common.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = (webpackEnv) => { const isEnvDevelopment = webpackEnv === 'development'; const isEnvProduction = webpackEnv === 'production'; return { mode: webpackEnv, entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [] }, plugins: [ new HtmlWebpackPlugin(), ], }; };html-webpack-plugin还可以添加一个模板文件,让html-webpack-plugin根据模板文件生成html文件。 我们在根目录下创建一个public文件夹,在文件夹下创建一个index.ejs: // public/index.ejs DOCTYPE html> Webpack5然后在插件中引入模板: // webpack/webpack.common.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = (webpackEnv) => { ... return { ... plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, '../public/index.ejs') }), ], }; };注意::这里我引用.html后缀的模板,html-webpack-plugin始终无法正常的生成html,然后改为了.ejs后就正常了。 在package.json中配置启动,打包命令然后我们在package.js中来配置启动和打包的命令: { "name": "fcc-template-typescript", "version": "1.0.0", "description": "", "private": true, "scripts": { "build": "webpack --config ./webpack/webpack.prod.js", "start": "webpack server --config ./webpack/webpack.dev.js" }, "keywords": [], "author": "", "license": "ISC", ... }我们在scripts中添加了build和start命令,build用于打包发布,start用以开发时启动项目。 然后我们命令行中进入到项目的更目录下,运行:npm start 或者 yarn start命令来启动项目。 加载CSS我们知道webpack本身只支持处理js和json类型的文件,如果我们想处理其它类型的文件,则需要使用相应的loader。 提前列出需要使用到的loader: style-loader css-loader postcss-loader安装: npm install --save-dev style-loader css-loader postcss-loader postcss postcss-preset-env对于css文件,则需要添加:css-loader: webpack.common.js // webpack/webpack.common.js ... module.exports = (webpackEnv) => { ... return { ... module: { rules: [ { test: /.css$/i, use: ["css-loader"], }, ], }, }; };index.js // src/index.js import './index.css';index.css #root { color: red; }此时我们运行发现文字并没有添加颜色,这是为什么? 因为css-loader只负责解析css文件,解析完成后会返回一个包含了css样式的js对象: 我们需要css样式生效,则需要将css样式插入到dom中,那么又需要安装自动插入样式的loader:style-loader。 webpack.common.js // webpack/webpack.common.js ... module.exports = (webpackEnv) => { ... return { ... module: { rules: [ { test: /.css$/i, use: ["style-loader", "css-loader"], }, ], }, }; };这里需要注意,loader的执行顺序是倒序执行(从右向左或者说从下向上),我们需要先使用css-loader解析css生成js对象后,将css对象交给style-loader,style-loader会创建style标签,将css样式抽取出来放在style标签中,然后插入到head中。 在不同浏览器上css的支持是不一样的,所以我们需要使用postcss-loader来做css的兼容: webpack.common.js module: { rules: [ { test: /.css$/i, use: [ "style-loader", "css-loader", { // css兼容性处理 loader: 'postcss-loader', options: { postcssOptions: { plugins: [ [ 'postcss-preset-env', { autoprefixer: { flexbox: 'no-2009', }, stage: 3, }, ], ], }, } }, ], }, ], },在postcss中使用了postcss-preset-env插件来自动添加前缀。 加载image图像在webpack5之前我们使用url-loader来加载图片,在webpack5中我们使用内置的Asset Modules来加载图像资源。 在 webpack 5 之前,通常使用: raw-loader 将文件导入为字符串 url-loader 将文件作为 data URI 内联到 bundle 中 file-loader 将文件发送到输出目录资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader: asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。 asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。 asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。 asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。webpack.common.js module: { rules: [ { test: /\.(png|svg|jpg|jpeg|gif)$/, type: 'asset', generator: { filename: 'image/[name].[contenthash:8][ext][query]' } }, ] },添加generator属性自定义文件名与文件存放位置。 也可以在output中定义assetModuleFilename设置默认存放位置与文件名格式: output: { assetModuleFilename: 'asset/[name].[contenthash:8][ext][query]', } 加载fonts字体或者其他资源webpack.common.js module: { rules: [ { exclude: /\.(js|mjs|ejs|jsx|ts|tsx|css|scss|sass|png|svg|jpg|jpeg|gif)$/i, type: 'asset/resource', }, ] },我们通过排除其他资源的后缀名来加载其他资源。 兼容js:将es6语法转换为es5需要使用到的loader: babel-loader安装: npm install --save-dev babel-loader @babel/core @babel/preset-env需要用到的babel插件: @babel/plugin-transform-runtime @babel/runtime安装: npm install --save-dev @babel/plugin-transform-runtime npm install --save @babel/runtimewebpack.common.js module: { rules: [ { test: /\.js$/, include: path.resolve(__dirname, './src'), use: [ { loader: 'babel-loader', options: { presets: [ [ "@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3, } ] ], } }, ], }, ] },这里我们会使用babel的插件:@babel/preset-env,它是转译插件的集合。 比如说我们使用了箭头函数,浏览器是不识别的需要转译成普通函数,那么我们就需要添加babel插件:@babel/plugin-transform-arrow-functions来处理箭头函数,如果我们使用了很多es6的api,都需要手动添加插件,这样会非常麻烦,babel为了简便开发者的使用,将所有需要转换的es6特性的插件都集合到了@babel/preset-env中。 在使用@babel/preset-env我们需要配置corejs属性,什么是corejs? babel只支持最新语法的转换,比如:extends,但是它没办法支持最新的Api,比如:Map,Set,Promise等,需要在不兼容的环境中也支持最新的Api那么则需要通过Polyfill的方式在目标环境中添加缺失的Api,这时我们就需要引入core-js来实现polyfill。 useBuiltIns则是告诉babel怎么引入polyfill。 当选择entry时,babel不会引入polyfill,需要我们手动全量引入: import "core-js"; var a = new Promise();当选择usage时,babel会根据当前的代码自动引入需要的polyfill: import "core-js/modules/es.promise"; var a = new Promise();但是我们发现这样使用polyfill,会污染全局对象,如下: "foobar".includes("foo"); 使用polyfill后,会在String的原型对象上添加includes方法: String.prototype.includes = function() {}如果我们使用了其它插件也在原型对象上添加了同名方法的,那就会导致出现问题。 这时我们则可以使用@babel/plugin-transform-runtime插件,通过引入模块的方式来实现polyfill: module: { rules: [ { test: /\.js$/, use: [ { loader: 'babel-loader', options: { presets: [ "@babel/preset-env", ], plugins: [ [ '@babel/plugin-transform-runtime', { "helpers": true, "corejs": 3, "regenerator": true, } ] ], } }, ], }, ] },我们来看下效果: "foobar".includes("foo");转译后: var _babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_1___default = __webpack_require__.n(_babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_1__); _babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_1___default()(_context = "foobar").call(_context, "foo");可以看到转译后includes的实现是通过调用了runtime—corejs3中的includes方法。 通过上面我们知道了@babel/plugin-transform-runtime的作用,我们再来看看它常用的配置属性。 helpers,我们将helpers先设置为false来看看编译后的效果。 class Test {}转译后: function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Test = function Test() { _classCallCheck(this, Test); };我们看到在转译后,在顶部添加了一个_classCallCheck工具函数,如果打包后有多个文件,每个文件中都是用了class,那么在顶部都会生成同样的_classCallCheck工具函数,这会使我们最后打包出来的文件体积变大。 我们将helpers设置为true,再来看转译后的效果: var _babel_runtime_corejs3_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); var Test = function Test() { (0,_babel_runtime_corejs3_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0__["default"])(this, Test); };我们看到_classCallCheck函数通过模块的方式被引入,这样就使babel通用的工具函数能够被复用,从而减小文件打包后的体积。 corejs:指定依赖corejs的版本进行polyfill。 regenerator:在我们使用generate时,会在全局环境上注入generate的实现函数,这样会造成全局污染,将regenerator设置true,通过模块引入的方式来调用generate,避免全局污染: function* test() { yield 1; }regenerator设置为false时: function test() { return regeneratorRuntime.wrap(function test$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return 1; case 2: case "end": return _context.stop(); } } }, _marked); }可以看到regeneratorRuntime这个对象是直接使用的,并没有引入,那么它肯定就是存在于全局环境上。 regenerator设置为true时: function test() { return _babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_0___default().wrap(function test$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return 1; case 2: case "end": return _context.stop(); } } }, _marked); }可以看到,这次使用的generate函数是从runtime-corejs3中导出引用的。 注意:还需要在package.json中配置目标浏览器,告诉babel我们要为哪些浏览器进行polyfill: // package.json { "name": "webpack5", "version": "1.0.0", ... "browserslist": { // 开发时配置,针对较少的浏览器,使polyfill的代码更少,编译更快 "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ], // 生产的配置,需要考虑所有支持的浏览器,支持的浏览器越多,polyfill的代码也就越多 "production": [ ">0.2%", "not dead", "not op_mini all" ] } } 进阶配置完成了webpack的基本配置后,我们再来配置一些更高级的。 加载css modules什么是css modules? 我是这么理解的:每个css文件都有自己的作用域,css文件中的属性在该作用域下都是唯一的。 在我们有多个组件时,每个组件都有相对应的css文件,其中的属性名称难免会有重名的,我们直接使用的话,后者则会覆盖前者,只会有一个样式生效。我们通过css modules对属性名通过hash值或者路径字符串的形式进行重命名,保证每个属性名都是唯一的,只会作用在本身的组件上,而不会影响到其它组件。 直接在css-loader中添加modules属性: { test: /\.module\.css$/, use: [ ... { loader: 'css-loader', options: { modules: { localIdentName: '[hash:base64:8]', } } } ], }, 加载sasssass是一款强化css的辅助工具,它在css基础上增加了变量,嵌套,混合,导入等功能,能够使我们更好的管理样式文件,更高效的开发项目。 安装: npm install sass-loader sass --save-dev在webpack中需要添加sass-loader,来对sass文件进行处理,将sass文件转化为css文件。 { test: /\.(scss|sass)$/, use: [ ... 'sass-loader' ], }, 配置React我们在写react代码的时候,使用了jsx语法,但是浏览器并不认识jsx语法,我们需要先对jsx语法进行转换为浏览器认识的语法React.createElement(...)。 需要的babel插件: @babel/preset-react安装: npm install --save-dev @babel/preset-react使用: { loader: 'babel-loader', options: { presets: [ "@babel/preset-env", [ "@babel/preset-react", { runtime: 'automatic', } ], ], } },在以前旧版本中,我们在使用jsx语法时,必须要引入: import react from 'react';在最新的版本中,我们将runtime设置为automatic,就可以省略这一步,babel会自动为我们导入jsx的转换函数。 配置Typescript浏览器是不支持ts的语法的,我们需要先将ts文件进行编译,转换为js后浏览器才能够识别。 安装: npm install --save-dev @babel/preset-typescript使用: { loader: 'babel-loader', options: { presets: [ "@babel/preset-env", [ "@babel/preset-react", { runtime: 'automatic', } ], "@babel/preset-typescript", ], } },还需要在项目根目录下添加tsconfig.json: { "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", }, "include": [ "src", ] }具体配置可以查看ts官网。 配置ESLint多人开发时,我们希望每个人写的代码风格都是统一的,那么则需要ESLint来帮助我们实现。 安装: yarn add -D eslint eslint-webpack-plugin yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser yarn add -D eslint-config-airbnb eslint-config-airbnb-typescript yarn add -D eslint-plugin-import eslint-plugin-jsx-a11y yarn add -D eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks这里我们选用的是 eslint-config-airbnb 配置,它对 JSX、Hooks、TypeScript 及 A11y 无障碍化都有良好的支持,可能也是目前最流行、最严格的 ESLint 校验之一。 接下来,创建 ESLint 配置文件 .eslintrc.js: module.exports = { root: true, parser: '@typescript-eslint/parser', extends: [ 'airbnb', 'airbnb-typescript', 'airbnb/hooks', 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:react/jsx-runtime', ], parserOptions: { project: './tsconfig.json', }, ignorePatterns: [".*", "webpack", "public", "node_modules", "dist"], // 忽略指定文件夹或文件 rules: { // 在这里添加需要覆盖的规则 "react/function-component-definition": 0, "quotes": ["error", "single"], "jsx-quotes": ["error", "prefer-single"] } };到这里eslin配置完成,但是需要我们每次都运行命令去检查和修复代码的问题,这样比较麻烦,所以我们使用webpack的插件:eslint-webpack-plugin来自动查找和修复代码中的问题: { plugins: [ new ESLintPlugin({ extensions: ['.tsx', '.ts', '.js', '.jsx'], fix: true, // 自动修复错误代码 }), ] } 在命令行中提示ts的错误在打包的过程中我们发现,代码中提示ts的错误依然能打包成功,这不是我们期望的结果。我们期望的是当ts在代码中显示错误,那么打包时也应该报错。 安装: npm instal --save-dev fork-ts-checker-webpack-plugin使用: const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); plugins: [ new ForkTsCheckerWebpackPlugin({ typescript: { configFile: path.resolve(__dirname, '../tsconfig.json') } }); ] 配置别名和扩展名在引入文件的时候会这样写: import demo from 'demo.js';我们可以在webpack中配置扩展名,之后再引入文件则可以省略文件的后缀名: resolve: { extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'], }当我们在引入一些文件时,它的路径比较长,写起来非常麻烦,而且不易阅读, 比如: import demo from './src/xxx/xx/components/demo';我们则可以在webpack中配置别名,来达到缩短文件路径的目的: resolve: { alias: { 'components': path.resolve(__dirname, '../src/xxx/xx/components/'), }, }之后我们就可以这样引入文件: import demo from 'components/demo'; 优化我们优化可以分为两个方面,一个是开发是的优化,一个是打包时的优化。 开发时的优化 sourcemap在开发时,我们需要对代码进行调式和错误定位,希望能够准确的定位到源码的位置上,那么我们则需要配置sourcemap: webpack.common.js devtool: 'cheap-module-source-map',配置好后,当代码报错,浏览器中就会显示报错的代码的准确信息。 配置缓存当我们启动项目时,每次都会重新构建所有的文件,但是有的文件是长期不变的,比如说在node_modules中的文件,并不需要每次都重新构建。 那么我们就讲这些长期不变的文件进行缓存: webpack.common.js cache: { type: "filesystem", // 使用文件缓存 },在下一次启动的时候,webpack首先会去读取缓存中的文件,以此来提高构建的速度。 babel的缓存:babel的缓存特性也是和webpack是一样的,在构建时,首先回去读取缓存,以此提高构建的速度: { loader: 'babel-loader', options: { cacheDirectory: true, } }缓存的文件默认会在node_modules下的.cache文件夹下。 开启HRM模块热替换什么是HRM?简单说就是当有模块被修改了,那么则会立即刷新这个模块,但是其他的模块不会刷新。 a.js -> b.jsa文件中引用了b文件,在没有开启HRM的情况下,我们修改了b文件,那么整个页面都会刷新。 在开启了HRM后,修改了b文件,b文件会马上刷新,但是a文件是不会刷新的。 使用: webapck.common.js devServer: { hot: true }在需要热更新的文件中添加以下代码: if(module && module.hot) { module.hot.accept() // 接受自更新 }但是在我们开发过程中不可能每个文件手动添加,而且在打包上线的时候是不需要热更新的代码的。 所以出现了一些自动添加热更新函数的插件: React Hot Loader: 实时调整 react 组件。 Vue Loader: 此 loader 支持 vue 组件的 HMR,提供开箱即用体验。 Elm Hot webpack Loader: 支持 Elm 编程语言的 HMR。 Angular HMR: 没有必要使用 loader!直接修改 NgModule 主文件就够了,它可以完全控制 HMR API。 Svelte Loader: 此 loader 开箱即用地支持 Svelte 组件的热更新。对于React来说,已经不使用React Hot Loader这个loader,而是使用react-refresh. 安装: yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh使用: const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); // react热更新 { module: { rules: [ { test: /\.(js|jsx|ts|tsx)$/, include: path.resolve(__dirname, '../src'), use: [ { loader: 'babel-loader', options: { plugins: [ isEnvDevelopment && 'react-refresh/babel', ].filter(Boolean), } }, ] } ] }, plugins: [ isEnvDevelopment && new ReactRefreshWebpackPlugin(), ] }在babel-load的plugins中添加react-refresh/babel,然后在webpack的plugins中添加@pmmmwh/react-refresh-webpack-plugin。有一点需要注意,热更新只在开发环境开启,如果在生产环境开启了,会将热更新的代码一起打包,但是它对于我们生产环境的代码来说没有任何作用。 对于css的热更新来说,在我们使用的style-loader的内部已经实现了HRM。 打包时的优化 抽离cssmini-css-extract-plugin插件会将js中的css提取到单独的css文件中,并且支持css和sourcemaps的按需加载。 安装: npm install --save-dev mini-css-extract-plugin使用: const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 将css从js中分离为单独的css文件 { module: { rules: [ { test: /\.css$/, use: [ isEnvProduction ? MiniCssExtractPlugin.loader: 'style-loader', 'css-loader' ], }, ] }, plugins: [ new MiniCssExtractPlugin(), ] }通过环境区别,在开发环境使用style-loader,在生产环境使用mini-css-extract-plugin。 代码分离在开发过程中,同一个文件难免会被多个文件引用,在打包后,这个被引用的文件会重复存在于引用了它的文件当中,我们需要将它打包成独立的文件来达到复用的目的。 使用splitChunks: { optimization: { splitChunks: { chunks: 'all' } } } 最小化入口chunk的体积通过配置optimization.runtimeChunk,将入口文件中运行时的代码提出来单独创建一个chunk,减小入口chunk的体积。 { optimization: { runtimeChunk: 'single' } } 压缩js通常压缩js代码我们会使用terser-webpack-plugin,在webpack5中已经内置了该插件,当mode为production时会自动启用。 如果我们想自定义的话: const TerserPlugin = require('terser-webpack-plugin'); module.exports = { optimization: { minimizer: [ new TerserPlugin({...}); ], }, }; 压缩css我们会使用css-minimizer-webpack-plugin插件。 它与optimize-css-assets-webpack-plugin 相比,在 source maps 和 assets 中使用查询字符串会更加准确,支持缓存和并发模式下运行。 安装: npm install css-minimizer-webpack-plugin --save-dev使用: const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); module.exports = { optimization: { minimizer: [ new CssMinimizerPlugin(); '...' ], }, };这里注意一点,如果我们只想添加额外的插件与默认插件一起使用,需要添加'...',表示添加默认插件。 dll使用DllPlugin将不会频繁更改的代码单独打包生成一个文件,可以提高打包时的构建速度。 使用: 首先我们新建一个webapck.dll.js文件,将会不频繁更改的包添加在入口: const paths = require('./paths'); const webpack = require('webpack'); module.exports = { mode: 'production', entry: { vendor: [ 'react', 'react-dom' ] }, output: { path: paths.dllPath, filename: paths.dllFilename, library: '[name]_dll_library' }, plugins: [ // 使用DllPlugin插件编译上面配置的NPM包 // 会生成一个json文件,里面是关于dll.js的一些配置信息 new webpack.DllPlugin({ path: paths.dllJsonPath, name: '[name]_dll_library' }) ] };然后我们在package.json处添加打包命令: { "name": "webpack5", "version": "1.0.0", "description": "", "private": true, "scripts": { "dll": "webpack --config ./webpack/webpack.dll.js" ... }, ... }然后我们运行:npm run dll 最后会在项目根目录生成一个dll文件夹,其中会生成一个js文件,包含了我们需要单独打包的模块:react,react-dom,并且还需生成一个包含被打包模块信息的json文件。 - dll - vendor.dll.js - dll.manifest.json然后我们还需要干两件事: 在上线打包时告诉webpack,不要将我们dll的模块进行打包 在打包成功后的js文件中是不会包含我们dll的模块,所以我们需要将dll出来的js文件引入。webpack.common.js 我们使用DllReferencePlugin来排除dll的模块: new webpack.DllReferencePlugin({ manifest: paths.dllJsonPath })我们需要将dll出来的json文件引入,json文件中包含了已经被打包的模块的信息,在webpack打包时就会排除这些模块。 然后我们使用add-asset-html-webpack-plugin来将dll出来的文件引入: 安装: yarn add -D add-asset-html-webpack-plugin使用: const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); new AddAssetHtmlWebpackPlugin({ filepath: path.resolve(__dirname, '../dll/vendor.dll.js'), publicPath: '' })这里需要注意一点,在我引入js文件后,进行了打包运行,但是发现在运行时找不到vendor.dll.js,查看了路径发现多了一层:auto,需要设置publicPath为空字符串解决。 tree shaking - 树摇关于树摇,简单的理解的话就是再打包的时候将没有使用的js代码排除掉。 开启树摇:只需要将mode设置为production,tree shaking就会自动开启。 有些时候我们会开发一些插件,里面会有很多方法是提供给别人使用的,在插件内部是没有使用,在打包的时候就会被tree shaking掉。这时我们需要在package.js中声明一下sideEffects属性: { "name": "webpack5", "version": "1.0.0", "description": "", "private": true, "sideEffects": ["*.js", "*.css"] ... }通过sideEffects告诉webpack在我声明的文件中是有副作用的,不要进行tree shaking。 清除未使用的css清除未使用的css,可以理解为css的tree shaking,我们使用purgecss来实现。 因为我们经常会使用css模块,所以需要安装@fullhuman/postcss-purgecss yarn add -D @fullhuman/postcss-purgecss使用: { loader: 'postcss-loader', options: { postcssOptions: { plugins: [ ... isEnvProduction && [ '@fullhuman/postcss-purgecss', // 删除未使用的css { content: [ paths.appHtml, ...glob.sync(path.join(paths.appSrc, '/**/*.{tsx,ts,js,jsx}'), { nodir: true }) ], } ] ].filter(Boolean), }, } },在postcss-loader添加该插件就可以了。 多线程我们可以在工作比较繁重,花费时间比较久的操作中,使用thread-loader开启多线程构建,能够提高构建速度。 安装: npm install --save-dev thread-loader使用: { test: /\.(js|jsx|ts|tsx)$/, include: paths.appSrc, use: [ { loader: "thread-loader", options: { workers: 2, workerParallelJobs: 2 }, }, ] }webpack 官网 提到 node-sass 中有个来自 Node.js 线程池的阻塞线程的 bug。 当使用 thread-loader 时,需要设置 workerParallelJobs: 2。 打包前清空输出文件夹在webpack5之前我们使用clean-webpack-plugin来清除输出文件夹,在webpack5自带了清除功能。 使用: output: { clean: true, }在output中配置clean属性为true 懒加载懒加载也可以叫做按需加载,本质是将文件中的不会立即使用到的模块进行分离,当在使用到的时候才会去加载该模块,这样的做法会大大的减小入口文件的体积,让加载速度更快。 使用方式则是用import动态导入的方式实现懒加载。 使用: import('demo.js') .then({default: res} => { ... });当webpack打包时,就会将demo文件单独打包成一个文件,当被使用时才会去加载。 可以使用魔法注释去指定chunk的名称与文件的加载方式。 指定chunk名称: import(/* webpackChunkName: "demo_chunk" */ 'demo.js') .then({default: res} => { ... });指定加载方式: import( /* webpackPreload: "demo_chunk", webpackPrefetch: true */ 'demo.js' ) .then({default: res} => { ... });我们来看下两种加载方式的区别: prefetch:会在浏览器空闲时提前加载文件 preload:会立即加载文件 使用CDN打包完成后,可以将静态资源上传到CDN,通过CDN加速来提升资源的加载速度。 webpack.common.js output: { publicPath: isEnvProduction ? 'https://CDNxxx.com' : '', },通过配置publicPath来设置cdn域名。 浏览器缓存浏览器缓存,就是在第一次加载页面后,会加载相应的资源,在下一次进入页面时会从浏览器缓存中去读取资源,加载速度更快。 webpack能够根据文件的内容生成相应的hash值,当内容变化hash才会改变。 使用: module.exports = { output: { filename: isEnvProduction ? "[name].[contenthash].bundle.js" : "[name].bundle.js", }, };只在生产环境开启hash值,在开发环境会影响构建效率。 优化工具这里简单介绍一下: progress-bar-webpack-plugin:打包时显示进度,但是会影响打包速度,需要斟酌使用,如果打包时间过长可以使用,否则不需要使用。 speed-measure-webpack-plugin:查看loader和plugin的耗时,可以对耗时较长的loader和plugin针对性优化。 webpack-bundle-analyzer:可以查看打包后各个文件的占比,来针对性的优化。注:以上都是webpack的plugin 总结 使用的loader处理css的loader: style-loader:插入css样式到dom中 css-loader:解析css文件生成js对象 postcss-loader: 处理css兼容问题 postcss-preset-env:postcss预置插件,处理css兼容性,自动添加浏览器内核前缀 @fullhuman/postcss-purgecss: 清除未使用的css样式 sass-loader:处理sass文件 less-loader:处理less文件 mini-css-extract-plugin: 使用该插件内部的loader,将css独立打包成一个文件,还需要添加该插件到plugins中。处理js|ts的loader: 1.babel-loader: 处理js兼容,将es6转es5,对新Api进行polyfill。 presets - babel预置插件: @babel/preset-env:bebal预置es6转es5的插件 @babel/preset-react:jsx语法转换 @/babel/preset-typescript: ts语法转jsplugins - babel插件 @babel/plugin-transform-runtime:将babel的工具函数以模块的方式引用以达到重复使用的目的。将polyfill的方法以模块的方式引用,来处理polyfill污染全局变量的问题。将genarate以模块的方式引用,来处理generate污染全局变量的问题。 react-refresh/babel:react组件热更新(HRM)处理资源文件:图片,字体文件等的loader url-loader:处理图片,设置options.limite属性,小于该属性值的图片转为base64(内联),大于则将图片发送至输出目录。(内置file-loader) file-loader:处理文件,将文件发送至输出目录 raw-loader:将文件导出为字符串webpack5中使用内置的Asset Module: asset/resource替代file-loader asset/inline替代url-loader asset/source替代raw-loader asset通过配置parser.dataUrlCondition.maxSize,来自动选择使用asset/resource(大于MaxSize)和asset/inline(小于maxSize)。 使用的webpack plugin html-webpack-plugin: 自动生成html文件,并且自动引用webpack构建好的文件。 mini-css-extract-plugin: 分离css单独打包成一个文件。 clean-webapck-plugin: 自动清除输出文件目录,被output.clean替代 copy-webpack-plugin: 复制文件到另外一个文件夹 compression-webpack-plugin: 压缩文件 webpack.DefinePlugin:注入全局变量 webpack.DllPlugin:生成打包一个含有打包模块信息的json文件 webpack.DllReferancePlugin:使用Dllplugin生成的json文件使webpack在打包时排除json文件中包含的模块。 add-asset-html-webpack-plugin:自定义添加bundle文件到html中,本文中是将dll生成的js文件,使用该插件添加到了html中。 eslint-webpack-plugin:代码格式校验 fork-ts-checker-webpack-plugin:在命令行中显示ts错误 @pmmmwh/react-refresh-webpack-plugin:react组件热更新(HRM) 关于webpack其它文章不了解Tapable?那你咋写的Webpack插件 面试官:你写过webpack插件吗? 来吧!一起肝一个CLI工具 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |