webpack入门与进阶:进阶用法(三)
自动清理构建目录
避免构建前每次都需要手动删除dist,可以通过clean-webpack-plugin
自动清理。 webpack4.0配置
npm install clean-webpack-plugin -D 复制代码
webpack.prod.js
// webpack.prod.js // 自动清理构建目录 const CleanWebpackPlugin = require('clean-webpack-plugin'); module.exports = { ... ... plugins: [ new CleanWebpackPlugin(), ] } 复制代码
webpack5.0配置, 无需下载插件官方文档
output: { path: path.resolve(__dirname, 'dist'), // path: path.join(__dirname, 'dist'), // 使用 [name]占位符 设置文件指纹 filename: '[name]_[chunkhash:8].js', // webpack5.0 清理构建目录配置 clean: true, }, 复制代码
自动添加CSS3前缀
不同浏览器,内核不一样,css3需要补全不同的前缀,实现兼容问题。
使用PostCSS插件:autoprefixer自动补齐CSS3前缀
autoprefixer
是css的后置处理器,而less或者sass是css预处理器。预处理器是打包前去处理,而autoprefixer是代码打包生成之后,才开始处理。
可以在caniuse查看样式兼容问题。
通常autoprefixer会跟postcss配合使用。
npm install --save-dev postcss-loader postcss配合使用。 autoprefixer 复制代码
postcss-loader
执行顺序必须保证在 css-loader
之前,建议还是放在 less或者 sass 等预处理器之后更好。即 loader 顺序:
less-loader -> postcss-loader -> css-loader -> style-loader 或者 MiniCssExtractPlugin.loader
webpack.prod.js
{ test: /\.less$/, use: [ // 'style-loader', MiniCssExtractPlugin.loader, 'css-loader', 'less-loader', { loader: "postcss-loader" } ] }, 复制代码
根目录新建postcss.config.js
module.exports = { plugins: [ require('autoprefixer')() ] } 复制代码
package.json
"browserslist": [ "defaults", "not ie < 11", "last 2 version", "> 1%", "ios 7", "last 3 ios version" ] 复制代码
解决webpack autoprefixer配置不生效
移动端CSS px 自动转换成rem
为适配不同的机型,实现响应式布局,可使用rem作为尺寸单位。
使用 px2rem-loader。
页面渲染是计算根元素的font-size。lib-flexible库
现在大部分使用viewport去兼容不同的浏览器。
viewport 待更新...
资源内联
资源内联的意义
代码层面:
页面框架的初始化脚本
上报相关打点
css内联避免页面闪动
请求层面:减少HTTP网络请求数
小图片或者字体内联(url-loader)
HTML和JS内联
raw-loader内联html,js
CSS内联
方案一:借助style-loader 把 CSS 插入到 DOM 中。
方案二:html-inline-css-webpack-plugin
多页面应用
每⼀次⻚⾯跳转的时候,后台服务器都会给返回⼀个新的 html ⽂档,这种类型的⽹站也就是多⻚⽹站,也叫做多⻚应⽤。
动态获取entry和设置html-webpack-plugin数量
利用glob.sync,以同步的方式查询文件。
glob.sync(path.join(__dirname, './src/*/index.js'));
查询根目录下的文件夹下的所有index.js文件
npm i glob -D 复制代码
webpack.prod.js
const glob = require('glob'); const setMPA = () => { const entry = {}; const htmlWebpackPlugins = []; // 查找出这个项目下的所有index.js文件 const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js')); // console.log(entryFiles, 'entryFiles') // [ '/Users/**/program/webpack-demo/src/index/index.js','/Users/**/program/webpack-demo/src/search/index.js' ] Object.keys(entryFiles).map(index => { const entryFile = entryFiles[index]; const match = entryFile.match(/src\/(.*)\/index\.js/); // 对应入口文件夹名称 const pageName = match && match[1]; entry[pageName] = entryFile; htmlWebpackPlugins.push( new HtmlWebpackPlugin({ // 模板所在位置 template: path.join(__dirname, `src/${pageName}/index.html`), // 指定打包后的文件名称 filename: `${pageName}.html`, // 指定生成的html需要哪些chunk chunks: [pageName], inject: true, minify: { html5: true, collapseWhitespace: true, preserveLineBreaks: false, minifyCSS: true, minifyJS: true, removeComments: false } }) ); }) return { entry, htmlWebpackPlugins } } const { entry, htmlWebpackPlugins } = setMPA(); module.exports = { entry, ... plugins: [ ...htmlWebpackPlugins, // 压缩css new MiniCssExtractPlugin({ filename: '[name]_[contenthash:8].css' }), new OptimizeCSSAssetsPlugin(), ] } 复制代码
Source map
作⽤:通过 source map 定位到源代码。
简单说,Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。
基本上开发环境直接用source-map。
production环境就把source-map添加到Error Reporting Tool(e.g. Sentry)上。这样既不直接暴露源代码,也能方便解决production环境遇到的bug。
source map科普⽂:www.ruanyifeng.com/blog/2013/0… \
source-map关键字
eval:使用eval包裹模块代码
source-map:产生.map文件
cheap 不包含列信息
inline 将.map作为DataURI嵌入,不单独生成.map文件
module: 包含loader的sourcemap
souce-map类型
devtool配置对应打包后的文件差异
// webpack.prod.js module.exports = { ... mode: 'none', devtool: 'eval' } 复制代码
1、设置为eval,打包后,会有eval包裹
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _helloWorld__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);\n\nsetTimeout(function () {\n document.write((0,_helloWorld__WEBPACK_IMPORTED_MODULE_0__.helloWorld)());\n});\n\n//# sourceURL=webpack://webpack-demo/./src/index/index.js?"); 复制代码
2、设置为source-map
js会和map内容进行分离 3、设置为inline-source-map
js会和map内容不会进行分离
基础库分离
如果需要引用一个库,但是又不想让webpack打包(减少打包的时间),并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用(一般都以import方式引用使用),那就可以通过配置externals。
这样做的目的就是将不怎么需要更新的第三方库脱离webpack打包,不被打入bundle中,从而减少打包时间,但又不影响运用第三方库的方式,例如import方式等。
方式一:使用html-webpack-externals-plugin
思路:将react、react-dom基础包通过cdn引入,不打入bundle中
方法:使用html-webpack-externals-plugin
npm install --save-dev html-webpack-externals-plugin // npm@6+ npm install html-webpack-externals-plugin -D --legacy-peer-deps 复制代码
1、配置webpack.prod.js
// 分离基础库 const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin'); module.export = { ... plugins: [ ...htmlWebpackPlugins, // 压缩css new MiniCssExtractPlugin({ filename: '[name]_[contenthash:8].css' }), new OptimizeCSSAssetsPlugin(), new HtmlWebpackExternalsPlugin({ externals: [ { module: 'react', entry: 'https://11.url.cn/now/lib/16.2.0/react.min.js', global: 'React', }, { module: 'react-dom', entry: 'https://11.url.cn/now/lib/16.2.0/react-dom.min.js', global: 'ReactDOM', }, ] }), ] } 复制代码
2、在src/search/index.html页面引入react react-dom包
<!DOCTYPE html> <html> <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> </head> <body> <div id="root"></div> // 引入react react-dom包 <script type="text/javascript" src="https://unpkg.com/react@17/umd/react.production.min.js"></script> <script type="text/javascript" src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script> </body> </html> 复制代码
分离react react-dom包之后,打包之后的体积大大缩小
关于安装可能出现的问题:
如果npm@6.0+,下载会报错,错误如下:
错误解决方案:
gitee.com/vincentyun/…
www.it1352.com/2315867.htm…
找了好久才解决,原来因为npm7.x对某些事情比npm6.x更严格。
通常,最简单的解决方法是将--legacy-peer-deps标志传递给 npm ( npm install --legacy-peer-deps ),或者使用npm@6。
如果这不能立即起作用,也许可以先删除node_modules和package-lock.json。它们将被重新创建。
提示:使用npm@6不需要卸载npm@7。使用npx指定npm的版本。例如:npx -p npm@6 npm i --legacy-peer-deps。
方式二:利用splitChunks进行公共脚本分离(去掉html-webpack-externals-plugin相关配置)
从 webpack v4 开始内置了SplitChunksPlugin
,直接通过optimization.splitChunks
配置 webpack.prod.js
const setMPA = () => { ... htmlWebpackPlugins.push( new HtmlWebpackPlugin({ chunks: ['vendors', pageName],//此处多配置一个vendors,与cacheGroups中的name对应 }) ) } module.exports = { ... optimization: { // splitChunks分离基础包 splitChunks: { cacheGroups: { commons: { test: /(react|react-dom)/, // vendors需要添加到htmlWebpackPlugins的chunk里 name: 'vendors', chunks: 'all' } } }, }, // source-map,产生.map文件 devtool: 'inline-source-map', } 复制代码
执行npm run build
编译打包之后,在html文件里,会引入这个vendors
tree shaking(摇树优化)
1、概念
1个模块可能有多个⽅法,只要其中的某个⽅法使⽤到了,则整个⽂件都会被打到bundle ⾥⾯去,tree shaking 就是只把⽤到的⽅法打⼊ bundle ,没⽤到的⽅法会在uglify 阶段被擦除掉。
2、使用
webpack 默认⽀持,在 .babelrc ⾥设置 modules: false 即可
production mode的情况下默认开启
3、要求
必须是ES6的语法,CJS的方式不支持
4、DCE (Dead code elimination)
代码不会被执行,不可到达
代码执行的结果不会被用到
代码只会影响死变量(只写不读)
if(false){ console.log('这段代码永远不会被执行') } 复制代码
原理
利⽤ ES6 模块的特点:
只能作为模块顶层的语句出现
import 的模块名只能是字符串常量
import binding 是 immutable的
代码擦除:uglify 阶段删除⽆⽤代码
(不懂......)
代码分割和动态import
一、代码分割
1、意义
对于⼤的 Web 应⽤来讲,将所有的代码都放在⼀个⽂件中显然是不够有效的,特别是当你的某些代码块是在某些特殊的时候才会被使⽤到。webpack 有⼀个功能就是将你的代码库分割成chunks(语块),当代码运⾏到需要它们的时候再进⾏加载。
2、适用场景
抽离相同代码到一个共享块
脚本懒加载,使得初始下载的代码更小
二、动态import
懒加载JS脚本的方式:
commonJS:require.ensure
es6:动态import(目前还没有原生支持,需要babel转换)
1、安装插件
npm install @babel/plugin-syntax-dynamic-import --save-dev
2、在.babelrc文件里加入这个插件
{ "presets": [ "@babel/preset-env", "@babel/preset-react" ], "plugins": [ "@babel/plugin-syntax-dynamic-import" ] } 复制代码
3、新建text.js
import React from 'react'; export default () => <div>动态 import</div> 复制代码
4、在src/search/index.js文件里引入
'use strict'; import React from 'react'; import ReactDom from 'react-dom'; import './search.less' import imgSrc from './images/2.png' class Search extends React.Component{ constructor() { super() this.state = { Text: null } } render() { // debugger const { Text } = this.state; return <div className="search-text"> <div>Search Text111</div> <img className="img" src={imgSrc} onClick={this.loadComponent.bind(this)}></img> {/* 渲染页面 */} { Text ? <Text/> : null } </div> } loadComponent() { // 动态引入文件 import('./text.js').then((Text) => { this.setState({ Text: Text.default, }) }) } } ReactDom.render( <Search/>, document.getElementById('root') ) 复制代码
5、执行npm run build,打包完成之后会多一个数字开头的js文件,就是动态引入的文件。开头的数字就是懒加载的id。
点击图片,就会加载js
在webpack中使用ESLint
⾏业⾥⾯优秀的 ESLint 规范实践
腾讯:
alloyteam团队 eslint-config-alloy
ivweb 团队:eslint-config-ivweb
一、webpack与CI/CD集成
(loading......)
二、webpack与ESLint集成
1、在react中集成 1.1 安装
npm install --save-dev eslint @babel/eslint-parser @babel/preset-react@latest eslint-plugin-react eslint-config-alloy 复制代码
npm install eslint-loader babel-eslint -D 复制代码
npm install eslint-config-airbnb -D 复制代码
1.2webpack.prod.js中加入eslint-loader
module.exports = { module: { rules: [ { test: /\.js$/, // 添加eslint-loader use: ['babel-loader', 'eslint-loader'], }, ] } } 复制代码
1.3 配置.eslintrc.js
eslint官网
module.exports = { parser: 'babel-eslint', // 继承 extends: ['airbnb'], // 环境变量 env: { browser: true, node: true, }, }; 复制代码
在webpack中打包组件和基础库
webpack 除了可以⽤来打包应⽤,也可以⽤来打包 js 库,实现⼀个⼤整数加法库的打包。
需要打包压缩版和⾮压缩版本
打包好的组件和基础库,⽀持 AMD/CommonJS/ESModule 模块引⼊,也支持直接通过script引入
如何将库暴露出去?
library:指定库的全局变量
libraryTarget:支持库引入的方式
创建一个简单的组件库
1、新建一个项目larger-num,初始化该项目
npm init -y 复制代码
npm install webpack webpack-cli 复制代码
在根目录下新建webpack.config.js
,src/index.js
2、src/index.js
导出一个方法
export default function add(a,b) { let i = a.length - 1; let j = b.length - 1; // 进位 let carry = 0; let ret = ""; while(i >= 0 || j >= 0) { let x = 0,y = 0, sum; if (i >= 0) { x = a[i] - '0'; i--; } if (j >= 0) { y = b[j] - '0'; j--; } sum = x + y + carry; if (sum >= 10) { carry = 1; sum -= 10; } else { carry = 0; } // 0+'' ret = sum + ret; } if (carry) { ret = carry + ret; } return ret; } // add('999','1') // add('1','999') // add('123','2123') 复制代码
3、配置打包信息webpack.config.js
webpack.docschina.org/configurati…
module.exports = { entry: { 'large-number': './src/index.js', 'large-number.min': './src/index.js', }, // 导出一个库 output: { filename: '[name].js', library: { // 配置库的名字 name: "largeNumber", // 配置将库暴露的方式 type: "umd", export: "default" }, // library: 'largeNumber', // libraryTarget: 'umd', }, mode: "production", } 复制代码
4、打包
添加一个script
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack" }, 复制代码
执行打包命令
npm run build 复制代码
打包结果
只打.min文件压缩
webpack.docschina.org/plugins/ter…
1、下载terser-webpack-plugin
npm install terser-webpack-plugin --save-dev 复制代码
2、配置webpack.config.js
const TerserPlugin = require("terser-webpack-plugin"); module.exports = { ... ... mode: "none", optimization: { minimize: true, minimizer: [ // 通过 include 设置只压缩 min.js 结尾的⽂件 new TerserPlugin({ include: /\.min\.js$/, }) ] }, } 复制代码
3、执行npm run build,可以看到文件大小有明显变化,dist目录下的生成了一个压缩的一个未压缩的文件
4、发布到npm
如何发布npm包
webpack实现SSR打包
页面打开过程(客户端渲染)
客户端渲染,页面整体的打包过程如下:
用户点击一个按钮,会加载一个新的webview,加载完新的webview之后,开始加载新的页面
页面开始加载,此时会出现一段时间的白屏时间,页面没有内容(因为还没加载html)
HTML加载成功,开始加载数据
此时会有loading图标等等,告诉用户页面正在加载
浏览器开始请求CSS、JS
解析和执行CSS,页面上就会出现一些样式
解析和执行JS,会执行JS的一些逻辑,比如请求图片资源,请求数据等等
数据加载成功,渲染成功开始,加载图片资源
图片加载成功,页面可交互
缺点:整个过程是串行过程,白屏时间长
服务端渲染(SSR)是什么
渲染: HTML + CSS + JS + Data -> 渲染后的 HTML
服务端渲染:
所有模板等资源都存储在服务端,可将多个请求数量,优化成一个,是一个并行的加载
客户端渲染是依赖用户的网络,而服务端渲染是利用内⽹机器拉取数据,加载速度更快
⼀个 HTML 返回所有数据
优势:
减少白屏时间
对于SEO友好
浏览器和服务器交互过程
具体过程:
页面请求开始,请求会到达服务端(server)
服务端(server)会拿到HTML模板(HTML templete)和页面数据(data),渲染引擎会将HTML templete和data会进行server render
server render之后,浏览器会解析并渲染拼装好的HTML文件,此时,用户可以看到页面
再加载并执行JS文件和其他资源文件,页面到达完全可交互的状态
客户端渲染 VS 服务端渲染
总结:
客服端渲染是一个前端的JS渲染,而服务端渲染是在node端等底层语言里进行的渲染
服务端渲染 (SSR) 的核⼼是减少请求
作者:丝绒拿铁有点甜
链接:https://juejin.cn/post/7031448248573231134