一、什么是WebPack?
简单地说,Webpack其最核心的功能就是 解决模板之间的依赖,把各个模块按照特定的规则和顺序组织在一起,最终合并成一个JS文件(比如bundle.js)。这个整个过程也常常被称为是模块打包。
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到 JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Sass, TypeScript等),并将其转换和打包为合适的格式供浏览器使用。
二、为什么要用Webpack
在开发Web页面或Web应用程序的时候,都习惯性的将不同资源放置在不同的文件目录之中,比如图片放置在images(或img)下,样式文件放置在styles(或css)中,脚本文件放在js和模板文件放置在pages中。一直以来,发布的时候都会一次性的将所有资源打包发布,不管这些资源用到了还是没用到(事实上很多时候自己都分不清楚哪资源被使用)。用一句话来描述就是:依赖太复杂,太混乱,无法维护和有效跟踪。比如哪个样式文件引用了a.img,哪个样式文件引用了b.img;另外页面到底是引用了a.css呢还是b.css呢?
而Webpack这样的工具却能很多好的解决它们之间的依赖关系,使其打包后的结果能运行在浏览器上。其目前的工作方式主要被分为两种:
将存在依赖关系的模块按照特定规则合并成为单个.js文件,一次性全部加载进页面 在页面初始时加载一个入口模块,其他模块异步加载
2.1 Webpack的主要优势:
webpack-dev-server搭建本地环境,进行热更新
• 预处理(Less,Sass,ES6,TypeScript,……) • 图片添加hash,方便线上CDN缓存 • 自动处理CSS3属性前缀 • 单文件,多文件打包 。。。。 • 有完备的代码分割解决方案 • 模块化开发(import,require) ,支持多种模块标准 • 压缩js代码 • 复制 • 按需加载
相比于Parcel、Rollup具有同等功能的工具而言,Webpack还具有其他的优势:
-
Webpack支持多种模块标准: 这对于一些同时使用多种模块标准的工程非常有用,Webpack会帮我们处理好不同类型模块之间的依赖关系
-
Webpack有完备的代码分割解决方案: 它可以分割打包后的资源,首屏只加载必要的部分,不太重要的功能放到后面动态地加载
-
Webpack可以处理各种类型的资源: 除了JavaScript之外,Webpack还可以处理样式、模板、图片等资源。开发者要做的只是将之些资源导入,而无需关注其他. 另外,Webpack还拥有一个强大的社区。
2.2 Webpack4与Webpack3版本的区别:
1. 增加了mode属性,可以是 development 或 production
"scripts": {
"dev": "webpack --mode development",
"build": "webpack --mode production"
}
通过mode, 你可以轻松设置打包环境。
如果你将 mode 设置成 development,你将获得最好的开发阶段体验。这得益于webpack针对开发模式提供的特性:
- 浏览器调试工具
- 注释、开发阶段的详细错误日志和提示
- 快速和优化的增量构建机制
如果你将mode设置成了 production, webpack将会专注项目的部署,包括以下特性:
- 开启所有的优化代码
- 更小的bundle大小
- 去除掉只在开发阶段运行的代码
- Scope hoisting和Tree-shaking
2. webpack4在loader,optimize上进行了很多改动
module.loaders改为module.rules
module.loaders.loader改成module.loaders.use
loader的'-loader'均不可省略
3. 插件和优化
webpack4删除了合并相同的文件CommonsChunkPlugin插件,它使用内置API optimization.splitChunks 和 optimization.runtimeChunk,这意味着webpack会默认为你生成共享的代码块。
其它插件变化如下: NoEmitOnErrorsPlugin 废弃,使用optimization.noEmitOnErrors替代,在生产环境中默认开启该插件。 ModuleConcatenationPlugin 废弃,使用optimization.concatenateModules替代,在生产环境默认开启该插件。 NamedModulesPlugin 废弃,使用optimization.namedModules替代,在生产环境默认开启。 uglifyjs-webpack-plugin升级到了v1.0版本, 默认开启缓存和并行功能。
之前是这样:
plugins : [
new webpack.optimize.CommonsChunkPlugin({
name : 'main',
children : true,
minChunks : 2,
})]
此段代码已经在webpack4的高版本里面被移除 换成了如下的配置选项:
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: "commons",
chunks: "initial",
minChunks: 2
}
}
}
}
三、Webpack5展望
npm install —save-dev webpack@next
官方描述:
- 使用持久化缓存提高构建性能;
- 使用更好的算法和默认值改进长期缓存(long-term caching);
- 清理内部结构而不引入任何破坏性的变化;
- 引入一些breaking changes,以便尽可能长的使用v5版本。
通俗版描述:
- 减小打包后的文件体积
- 按需加载支持文件名模式
- 使用long-term caching解决生产环境下moduleIds & chunkIds变化的问题
- 使用cache: {type: "filesystem"}配置实现持久化缓存,提高构建速度
- 优化minSize&maxSize的配置方式
- Node.js polyfills 自动加载功能被移除
四、从零开始构建你自己的开发环境
为了更好的理解Webpack能帮我们做什么,我打算从零开始构建一个属于自己的开发环境。可能在后面的内容中会涉及到很多关键词,比如Webpack、loaders、Babel、sourcemaps、Vue、React、TypeScript,CSS Modules等等。
配置版本说明:
node: v12.16.3
Yarn: 1.16.0
Webpack: 4.44.1
Webpack-cli: 3.3.12
演示的系统是window , 以及node 的下载,安装成以后,会自带npm命令,如果想体验yarn 命令 可以通过npm install yarn -g 或者下载安装
4.1 环境安装:
当使用webpack4时,确保使用 Node.js的版本 >= 8.9.4。因为webpack4使用了很多JS新的语法,它们在新版本的 v8 里经过了优化。
- 安装命令yarn或者npm的选择:
yarn的安装和使用参考:blog.csdn.net/x550392236/…
- 安装webpack以及webpack-cli (不推荐)
yarn global add webpack webpack-cli
或者: npm install webpack webpack-cli -g
可以帮助我们在命令⾏行⾥使用webpack等相关指令 webpack -v
- 卸载
npm uninstall webpack webpack-cli -g 或者
yarn global remove webpack webpack-cli
- 局部安装 项目内安装 (推荐)
yarn add webpack webpack-cli --dev/-D 或者
npm install webpack webpack-cli --save--dev/-D
--dev/-D 指安装到devDependencies:开发时的依赖包 --save/-S dependencies:运行程序时的依赖包
webpack -v //command not found 默认在全局环境中查找
npx webpack -v// npx帮助我们在项⽬目中的node_modules里查找webpack
- 指定版本安装:
npm info webpack//查看webpack的历史发布信息 npm install webpack@x.xx webpack-cli -D npm i -D 是 npm install --save-dev 的简写,是指安装模块并保存到
注意,在命令终端使用npm i安装依赖关系时,如果带后缀 -D(或--save-dev) 安装的包会记录在"devDependencies"下;如果使用 --save后缀安装的包会记录在"dependencies"下。
4.2 . webpack 配置文件
当我们使⽤用npx webpack index.js时,表示的是使⽤用webpack处理理打包,名为index.js的⼊口模块。默 认放在当前⽬目录下的dist⽬目录,打包后的模块名称是main.js,当然我们也可以修改 webpack有默认的配置⽂文件,叫webpack.config.js,我们可以对这个文件进行修改,进行行个性化配置
- 不使⽤用默认的配置⽂文件: webpackconfig.js
npx webpack --config webpackconfig.js
//指定webpack使⽤用webpack.config.js文件来作为 配置⽂文件并执行
- 修改package.json scripts字段:有过vue react开发经验的同学 习惯使⽤用npm run来启动,我们也 可以修改下
"scripts":{
"bundle":"webpack"
//这个地⽅不要添加npx ,因为npm run执⾏行的命令,会优先使⽤用项目工程里的包,效果和npx一样
}
npm run bundle
4.3. 初始化项目,生成package.json
进入到新创建的项目目录下,执行 yarn init -y 或者npm init 生成package.json 首先在你的本地创建一个项目,比如我这里创建了一个webpack-sample项目: mkdir webpack-sample && cd webpack-sample
进入到新创建的项目目录下,执行yarn init或者yarn init -y命令来初始化项目,执行完该命令之后,在你的命令终端会命令询问,你可以根据你自己的需要去输入你想要的内容,或者一路Enter键执行下去:
此时你的项目根目录下会增加一些文件和文件夹:
|--webpack-sample/ |----package.json |----package-lock.json
其中package.json文件里将包含一些项目信息:
而package-lock.json文件是当 node_modules/ 或 package.json 发生变化时自动生成的文件,它的主要功能是 确定当前安装的包的依赖,以便后续重新安装的时候生成相同的依赖,而忽略项目开发过程中有些依赖已经发生的更新。
4.4. 安装Webpack和初始配置Webpack
在这一步,先来安装Webpack。执行下面的命令安装Webpack配置所需要的包:
yarn add webpack webpack-cli webpack-dev-server --dev
此时打开package.json文件,你会发现在文件中有一个新增项 devDependencies:
{
// 其他项信息在这省略,详细请查看该文件
"devDependencies": {
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5",
"webpack-dev-server": "^3.7.2"
}
}
为了验证Webpack是否能正常工作,这个时候我们需要创建一些新的文件。在webpack-sample根目录下创建/src目录,并且在该目录下创建一个index.js文件:
mkdir src && cd src && touch index.js
我们在新创建的/src/index.js文件下添加一行最简单的JavaScript代码:
console.log("Hello, Webpack!(^_^)~~")
保存之后回到命令终端,第一次执行有关于Webpack相关的命令:
webpack src/index.js --output dist/bundle.js
执行完上面的命令后,如果看到下图这样的结果,那么要恭喜你,Webpack的安装已经成功,你可以在你的命令终端执行有关于Webpack相关的命令:
回到项目中,会发现项目根目下自动创建了一个/dist目录,而且该目录下包含了一个bundle.js文件:
执行完上面的命令之后,可以看到有相关的警告信息。那是因为Webpack4增加了mode属性,用来表示不同的环境。mode模式具有development,production 和 none三个值,其默认值是production 。也就是说,在执行上面的命令的时候,我们可以带上相应的mode属性的值,比如说,设置none来禁用任何默认行为
npx webpack src/index.js --output dist/bundle.js --mode none
执行到这里,只知道我们可以运行Webpack相关命令。并不知道/src/index.js的代码是否打包到/dist/bundle.js中。为此,我们可以在/dist目录下创建一个index.html
cd dist && touch index.html
并且将生成出来的bundle.js引入到新创建的index.html:
在浏览器中打开/dist/index.html,或者在命令行中执行:
yarn global add http-server 或者 npm i -g http-server
http-server dist
http-server是一个启动服务器的npm包,执行上面的命令之后,就可以在浏览器中访问http://127.0.0.1:8080/(访问的/dist/index.html),在浏览器的console.log控制台中,可以看到src/index.js的脚本输出的值:
或者通过vscode的插件,安装一个 live Server 也可以直接在vscode里直接起服务
不过,当你需要构建的东西越复杂,需要的标志就会越多。在某种程度上说,就会变得难以控制。这个时候我们就需要一个文件来管理这些配置。接下来我们需要创建一个webpack.config.js这样的一个文件,用来配置Webpack要做的事情。注意,这个文件是一个node.js文件,所以你可以在任何节点文件中执行任何你能够执行的操作。你也可以写成json文件,但是node文件更强大一些。
首先创建Webpack的配置文件,在webpack-sample根目录下创建一个/build目录,然后在该目录下添加一个名为webpack.config.js文件:
mkdir build && cd build && touch webpack.config.js
执行完上面的命令之后,你会发现你的项目文件目录结构变成下面这样了:
这个时候,新创建的webpack.config.js文件里面是一片空白,它就是Webpack的配置文件,将会导出一个对象的JavaScript文件。我们需要在这个文件中添加一些配置:
var webpack = require('webpack');
var path = require('path');
var DIST_PATH = path.resolve(__dirname, '../dist');
// 声明/dist的路径 为成绝对路径
module.exports = {
// 入口JS路径
// 指示Webpack应该使用哪个模块,来作为构建其内部依赖图的开始
entry: path.resolve(__dirname,'../src/index.js'),
// 支持单文件,多文件打包
// entry: './src/index.js', //方式一
// entry: ['./src/index.js','./src/main.js'], //方法二
//entry: {
// index:'./src/index.js',
// main:'./src/main.js'
// }
// 编译输出的JS入路径
// 告诉Webpack在哪里输出它所创建的bundle,以及如何命名这些文件
output: {
path: DIST_PATH, // 创建的bundle生成到哪里
filename: 'bundle.js', // 创建的bundle的名称
},
// 模块解析
module: { },
// 插件
plugins: [ ],
// 开发服务器
devServer: { }
}
Webpack配置是标准的 Node.js CommonJS模块,它通过require来引入其他模块,通过module.exports导出模块,由Webpack根据对象定义属性进行解析。
上面很简单,到目前为止只通过entry设置了入口起点,然后通过output配置了打包文件输出的目的地和方式。你可能也发现了,在配置文件中还有module、plugins和devServer没有添加任何东西。不需要太急,后面会一步一步带着大家把这里的内容补全的,而且随着配置的东西越来越多,整个webpack.config.js也会更变越复杂。
完成webpack.config.js的基础配置之后,回到package.json文件,并在"scripts"下添加配置如下
package.json
{
// ...
"scripts": {
"build": "webpack --config ./build/webpack.config.js"
},
}
这样做的,为让我们直接在命令终端执行相关的命令就可以实现相应的功能。比如上面配置的build,在命令终端执行: ⇒ npm run build
面的命令执行的效果前面提到的webpack src/index.js --output dist/bundle.js --mode none等同。同样有警告信息,主要是mode的配置没有添加。在上面的配置中添加:
{
"scripts": {
"build": "webpack --config ./build/webpack.config.js --mode production",
"test": "echo \"Error: no test specified\" && exit 1"
},
}
再次执行npm run build,不会再有警告信息。你可以试着修改/src/index.js的代码:
alert(`Hello, Webpack! Let's Go`);
重新编译之后,打开/dist/index.html你会发现浏览器会弹出alert()框:
为了开发方便,不可能通过http-server来启用服务。我们可以把这部分事件放到开发服务器中来做,对应的就是devServer,所以我们接着在webpack.config.js中添加devServer相关的配置:
五、 webpack-dev-server快速搭建本地运行环境
webpack-dev-server是一个用来快速搭建本地运行环境的工具。webpack-dev-server是一个小型的Node.js Express服务器,它使用webpack-dev-middleware来服务于webpack的包,除此自外,它还有一个通过Sock.js来连接到服务器的微型运行时.
命令简单webpack-dev-server或配置命令脚本快捷运行,模拟服务器运行情况,进行上线前调试。
安装: yarn add webpack-dev-server --dev
5.1 webpack-dev-server和webpack-dev-middleware的区别
- webpack-dev-server
webpack-dev-server实际上相当于启用了一个express的Http服务器+调用webpack-dev-middleware。它的作用主要是用来伺服资源文件。这个Http服务器和client使用了websocket通讯协议,原始文件作出改动后,webpack-dev-server会用webpack实时的编译,再用webpack-dev-middleware将webpack编译后文件会输出到内存中。适合纯前端项目,很难编写后端服务,进行整合。
- webpack-dev-middleware
webpack-dev-middleware输出的文件存在于内存中。你定义了 webpack.config,webpack 就能据此梳理出entry和output模块的关系脉络,而 webpack-dev-middleware 就在此基础上形成一个文件映射系统,每当应用程序请求一个文件,它匹配到了就把内存中缓存的对应结果以文件的格式返回给你,反之则进入到下一个中间件。
因为是内存型文件系统,所以重建速度非常快,很适合于开发阶段用作静态资源服务器;因为 webpack 可以把任何一种资源都当作是模块来处理,因此能向客户端反馈各种格式的资源,所以可以替代HTTP 服务器。事实上,大多数 webpack 用户用过的 webpack-dev-server 就是一个 express+webpack-dev-middleware 的实现。二者的区别仅在于 webpack-dev-server 是封装好的,除了 webpack.config 和命令行参数之外,很难去做定制型开发。而 webpack-dev-middleware 是中间件,可以编写自己的后端服务然后把它整合进来,相对而言比较灵活自由。
- webpack-hot-middleware
是一个结合webpack-dev-middleware使用的middleware,它可以实现浏览器的无刷新更新(hot reload),这也是webpack文档里常说的HMR(Hot Module Replacement)。HMR和热加载的区别是:热加载是刷新整个页面。
webpack.config.js
// 开发服务器
devServer: {
hot: true, // 热更新,无需手动刷新
contentBase: DIST_PATH, //热启动文件所指向的文件路径
// host: '0.0.0.0', // host地址
port: 8080, // 服务器端口
historyApiFallback: true, // 该选项的作用所用404都连接到index.html
proxy: {
"/api": "http://localhost:3000"
// 代理到后端的服务地址,会拦截所有以api开头的请求地址
}
}
有关于devServer更详细的配置参数描述
devServer:{
port:7788,
//控制端口
open:true
//是否自动打开默认浏览器
headers ://在所有响应中添加首部内容
historyApiFallback:true //使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html
host:'0.0.0.0' //指定使用一个 host。默认是 localhost。如果你希望服务器外部可访问,指定如下
hot:true //启用 webpack 的模块热替换特性
https:true //默认情况下,dev-server 通过 HTTP 提供服务。也可以选择带有 HTTPS 的 HTTP/2 提供服务
Info: true //是否要输出一些打包信息
noInfo: true //启用 noInfo 后,诸如「那些显示的 webpack 包(bundle)信息」的消息将被隐藏
proxy: //这样启用代理 ,https://www.webpackjs.com/configuration/dev-server/#devserver-proxy
progress : //将运行进度输出到控制台
publicPath : //此路径下的打包文件可在浏览器中访问,默认 publicPath是 "/"
//https://www.webpackjs.com/configuration/dev-server/#devserver-publicpath-
useLocalIp: true //是否在打包的时候用自己的IP
watchContentBase:true //告诉服务器监视devserver.contentbase选项提供的文件。文件更改将触发整页重新加载
}
和build类似,需要在package.json的scripts中添加相关的命令:
package.json
"scripts": {
"build": "webpack --config ./build/webpack.config.js --mode production",
"dev": "webpack-dev-server --config ./build/webpack.config.js ",
"test": "echo \"Error: no test specified\" && exit 1"
},
保存所有文件,在命令行中执行npm run dev就可以启动服务器:
你可以验证一下,修改/src/index.js:
document.addEventListener('DOMContentLoaded', () => {
const h1Ele = document.createElement('h1');
document.body.append(h1Ele);
h1Ele.innerText = 'Hello Webpack (^_^)'
h1Ele.style.color = '#f46';
})
保存该文件之后,浏览器会立刻刷新,你将看到修改之后的变化:
六. 多文件打包,glob配置动态入口
安装:yarn add glob --dev
const glob = require("glob");
const htmls = glob.sync('src/components/**/*.html');
扫描出入口页面模板的路径, 如src/components/index/index.html, 存放在 htmls 对象里
//入口文件
var SRC_PATH = path.resolve(__dirname,'../src');
var newEntries = {};
// var files = glob.sync(path.join(SRC_PATH,'js/*.js')); // 方式一
var files = glob.sync(SRC_PATH+'/js/*.js'); //方式二
files.forEach(function(file,index){
// var substr = file.split('/').pop().split('.')[0];
var substr = file.match(/src\/js\/(\S*)\.js/)[1];
newEntries[substr] = file;
})
多文件的时候,需要修改output输出文件里的 动态文件配置为[name]
[\s]---表示,只要出现空白就匹配;
[\S]---表示,非空白就匹配;
七. hash、chunkhash、contenthash区别
hash一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。但是在实际使用的时候,这几种hash计算还是有一定区别。 [hash:8] 可以用来控制输出多少位的hash
- Hash
Hash: hash是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值 filename: 'bundle.[name].[hash].js'
- chunkhash
chunkhash: 采用hash计算的话,每一次构建后生成的哈希值都不一样,即使文件内容压根没有改变。这样子是没办法实现缓存效果,我们需要换另一种哈希值计算方式,即chunkhash。
chunkhash和hash不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响。
filename: 'bundle.[name].[chunkhash].js'
- contenthash
contenthash: 在chunkhash的例子,我们可以看到由于index.css被index.js引用了,所以共用相同的chunkhash值。但是这样子有个问题,如果index.js更改了代码,css文件就算内容没有任何改变,由于是该模块发生了改变,导致css文件会重复构建。 这个时候,我们可以使用extra-text-webpack-plugin里的contenthash值,保证即使css文件所处的模块里就算其他文件内容改变,只要css文件内容不变,那么不会重复构建。
plugins:[
new extractTextPlugin('../css/bundle.[name].[contenthash].css')
]
八. 打包之前删除某个文件夹
rimraf插件
安装:yarn add rimraf --dev
在package.json的script直接使用,比如:
"build": "rimraf dist && webpack --config ./build/webpack.config.js --mode production",
或者:
"build": "rm -rf dist && webpack --config ./build/webpack.config.js --mode production"
node常用命令
touch 新建文件
rm -f 删除文件
mkdir 新建文件夹
rm -rf 删除文件夹
九. 优化Webpack配置
开发和生产环境相关的配置都集成在webpack.config.js一个文件中。为了更好的维护代码,把webpack.config.js拆分成三个部分:
- 公共配置: 把开发和生产环境需要的配置都集中到公共配置文件中,即webpack.common.js
- 开发环境配置: 把开发环境需要的相关配置放置到webpack.dev.js
- 生产环境配置: 把生产环境需要的相关配置放置到webpack.prod.js
先在/build目录下创建上面提到的三个配置文件。在命令终端执行下面的命令即可:
⇒ cd build && touch webpack.common.js webpack.dev.js webpack.prod.js
这个时候,整个项目目录结构变成下图这样:
遗留下来的webpack.config.js文件将会从/build目录中移除。
Object.assign 合并对象
为了更好的管理和维护这三个文件,需要安装一个webpack-merge插件: yarn add webpack-merge --dev 或者 npm i webpack-merge -D 执行完上面的命令之后,package.json文件中的devDependencies会增加
接下来分别给webpack.common.js、webpack.dev.js和webpack.prod.js文件添加相关的配置:
9.1 Webpack公共配置
在公共配置文件webpack.common.js文件中添加相应的配置:
const webpack = require('webpack');
const path = require('path');
const DIST_PATH = path.resolve(__dirname, '../dist/');
// 声明/dist的路径
module.exports = {
// 入口JS路径
// 指示Webpack应该使用哪个模块,来作为构建其内部依赖图的开始
entry: path.resolve(__dirname,'../src/index.js'),
// 编译输出的JS入路径
// 告诉Webpack在哪里输出它所创建的bundle,以及如何命名这些文件
output: {
path: DIST_PATH,
// 创建的bundle生成到哪里
filename: 'bundle.js',
// 创建的bundle的名称
},
// 模块解析
module: { },
// 插件
plugins: [ ]
}
9.2 Webpack开发环境配置
接着给Webpack开发环境配置文件webpack.dev.js添加下面的相关配置:
const webpack = require('webpack');
const path = require('path');
const {merge} = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const DIST_PATH = path.resolve(__dirname, '../dist/');
module.exports = merge(commonConfig, {
mode: 'development',
// 设置Webpack的mode模式
// 开发环境下需要的相关插件配置
plugins: [ ],
// 开发服务器
devServer: {
hot: true, // 热更新,无需手动刷新
contentBase: DIST_PATH,
// host: '0.0.0.0', // host地址
port: 8080, // 服务器端口
historyApiFallback: true, // 该选项的作用所用404都连接到index.html
proxy: {
"/api": "http://localhost:3000" // 代理到后端的服务地址,会拦截所有以api开头的请求地址
}
}
})
9.3 Webpack生产环境配置
继续给Webpack生产环境配置文件webpack.prod.js添加相关配置:
const webpack = require('webpack');
const path = require('path');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
module.exports = merge(commonConfig, {
mode: 'production',
// 设置Webpack的mode模式
// 生产环境下需要的相关插件配置
plugins: [ ],
})
随着后续添加相应的配置信息,那么这三个文件中的配置信息会越来越多, 修改完Webpack的配置之后,对应的package.json中的scripts中的信息也要做相应的调整:
package.json
{
// ... 其他配置信息请查看原文件
"scripts": {
"build": "webpack --config ./build/webpack.prod.js --mode production",
"dev": "webpack-dev-server --config ./build/webpack.dev.js --mode development --open",
"test": "echo \"Error: no test specified\" && exit 1"
},
}
这个时候重新在命令终端执行
// 执行build命令,重新打包 ⇒ npm run build // 执行dev命令 ⇒ npm run dev
这仅仅是最基础部分的优化,因为我们的配置还是最简单的,后续我们添加了别的配置之后,也会在相应的步骤做相应的优化。
十. 编译 Webpack项目中的html类型的文件
这个plugin曝光率很高,他主要有两个作用
- 为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题
- 可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口
安装:yarn add html-webpack-plugin --dev
首先在项目根目录新建 index.html,作为模板文件进行打包 在插件里配置:
plugin是用于扩展webpack的功能,各种各样的plugin几乎可以让webpack做任何与构建先关的事情。 plugin的配置很简单,plugins配置项接收一个数组,数组里的每一项都是一个要使用的plugin的实例,plugin需要的参数通过构造函数传入。
var htmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new htmlWebpackPlugin({
filename: path.resolve(DIST_PATH,'index.html'), //打包后的文件名
title: '树鱼虚拟充值生态服务平台', //打包后的页面title
template: path.resolve(__dirname,'../index.html'), //打包的模板文件
inject: true,
hash: true,
favicon: path.resolve(__dirname, '../fav.ico')
})
]
使用plugin的难点在于plugin本身的配置项,而不是如何在webpack中引入plugin,几乎所有webpack无法直接实现的功能,都能找到开源的plugin去解决,我们要做的就是去找更据自己的需要找出相应的plugin。
打包后title如何自动的添加到页面中呢?
只需要在模板文件的title中添加
<title><%= htmlWebpackPlugin.options.title %></title>
具体api配置说明如下:
var htmlWebpackPlugin = require('html-webpack-plugin');
new htmlWebpackPlugin({
filename: , //输出的html的文件名称,默认是index.html
title: '标题', //生成html文件的标题
template: , //指定你生成的文件所依赖哪一个html文件模板,模板类型可以是html、jade、ejs等
inject: true, //true body head false
//true 默认值,script标签位于html文件的 body 底部
//body script标签位于html文件的 body 底部,(同true)
//head script标签位于html文件的 head中
//false 不插入生成的js文件,只是单纯的生成一个 html 文件,这个几乎不会用到的
hash: true,
favicon: path.resolve(__dirname, './fav.ico')
//给你生成的html文件生成一个 favicon ,值是一个路径
//然后再生成的html中就有了一个 link 标签
//<link rel="shortcut icon" href="example.ico">
minify: //是否对HTML进行压缩,默认true压缩
cache:true //默认是true的,表示内容变化的时候生成一个新的文件
showErrors://当webpack报错的时候,会把错误信息包裹再一个pre中,默认是true
chunks: chunks, //chunks主要用于多入口文件,当你有多个入口文件,那就回编译后生成多个打包后的文件,那么chunks 就能选择你要使用那些js文件 ,
excludeChunks://排除掉一些js
})
minify
minify 的作用是对 html 文件进行压缩,minify 的属性值是一个压缩选项或者 false 。默认值为false, 不对生成的 html 文件进行压缩。 下面罗列了一些常用的配置:
plugins:[
new HtmlWebpackPlugin({
//部分省略,具体看minify的配置
minify: {
//是否对大小写敏感,默认false
caseSensitive: true,
//是否简写boolean格式的属性如:disabled="disabled" 简写为disabled 默认false
collapseBooleanAttributes: true,
//是否去除空格,默认false
collapseWhitespace: true,
//是否压缩html里的css(使用clean-css进行的压缩) 默认值false;
minifyCSS: true,
//是否压缩html里的js(使用uglify-js进行的压缩)
minifyJS: true,
//Prevents the escaping of the values of attributes
preventAttributesEscaping: true,
//是否移除属性的引号 默认false
removeAttributeQuotes: true,
//是否移除注释 默认false
removeComments: true,
//从脚本和样式删除的注释 默认false
removeCommentsFromCDATA: true,
//是否删除空属性,默认false
removeEmptyAttributes: true,
// 若开启此项,生成的html中没有 body 和 head,html也未闭合
removeOptionalTags: false,
//删除多余的属性
removeRedundantAttributes: true,
//删除script的类型属性,在h5下面script的type默认值:text/javascript 默认值false
removeScriptTypeAttributes: true,
//删除style的类型属性, type="text/css" 同上
removeStyleLinkTypeAttributes: true,
//使用短的文档类型,默认false
useShortDoctype: true,
}
}),
]
chunks
chunks主要用于多入口文件,当你有多个入口文件,那就回编译后生成多个打包后的文件,那么chunks 就能选择你要使用那些js文件
entry: {
index: path.resolve(__dirname, './src/index.js'),
devor: path.resolve(__dirname, './src/devor.js'),
main: path.resolve(__dirname, './src/main.js')
}
plugins: [
new httpWebpackPlugin({
chunks: ['index','main']
})
]
那么编译后:
<script type='text/javascript' src="index.js"></script>
<script type='text/javascript' src="main.js"></script>
而如果没有指定 chunks 选项,默认会全部引用。
excludeChunks
排除掉一些js,
entry: {
index: path.resolve(__dirname, './src/index.js'),
devor: path.resolve(__dirname, './src/devor.js'),
main: path.resolve(__dirname, './src/main.js')
}
plugins: [
new httpWebpackPlugin({
excludeChunks: ['devor.js']
})
]
那么编译后:
<script type=text/javascript src="index.js"></script>
<script type=text/javascript src="main.js"></script>
完整代码:
var pluginsAll2 = [];
var pages = glob.sync(path.join(htmlPagesPath,'**/*.html'));
pages.forEach(function(page){
var pagestr = page.match(/pages\/(\S*)\.html/);
var name = pagestr[1];
var plug = new htmlWebpackPlugin({
filename:path.resolve(buildPath,name+'.html'),
title:'测试',
template:path.resolve(htmlPagesPath,name+'.html'),
inject:true,
chunks:[name],
favicon: path.resolve(__dirname, './fav.ico')
})
pluginsAll2.push(plug);
})
十一. loaders
常见的loader有:
file-loader,url-loader,style-loader,css-loader,less-loader,sass-loader,babel-loader,raw-loader,vue-loader webpack >= v2.0.0 开始默认支持json文件的导入
webpack是模块打包工具,⽽模块不仅是js,还可以是css,图片或者其他格式 但是webpack默认只知道如何处理js模块,那么其他格式的模块处理,和处理⽅式就需要loader
如何在webpack中配置第三方loader?
配置第三方loader,需要在webpack的配置文件中新增一个module节点,节点中是一个一个的规则集合,集合名字是rules,需要添加loader就在rules的集合中新增一个规则;每个规则必须的两个配置:
(1)test :test搭配的是键值对,值是一个正则表达式,用来匹配要处理的文件类型; (2)use :用来指定使用哪个loader模块来打包处理该文件;
module:{
rules:[
{
test:/\.xxx$/,
use:{
loader: 'xxx-load'
}
}
]
}
当webpack处理到不认识的模块时,需要在webpack中的module处进行配置,当检测到是什么格式的 模块,使⽤用什么loader来处理
11.1 file-loader:处理静态资源模块
1、作用:file-loader可以用来帮助webpack打包处理一系列的图片文件;比如:.png 、 .jpg 、.jepg等格式的图片;
2、使用file-loader打包图片的结果:使用file-loader打包的图片会给每张图片都生成一个随机的hash值作为图片的名字;
loader: file-loader 原理理是把打包入口中识别出的资源模块,移动到输出目录,并且返回⼀个地址名称
所以我们什么时候用file-loader呢? 场景:就是当我们需要模块,仅仅是从源代码挪移到打包⽬目录,就可以使⽤用file-loader来处理理, txt,svg,csv,excel,图片资源等
yarn add file-loader -D 或者 npm install file-loader -D
案例:
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/,
//use使用一个loader可以用对象,字符串,两个loader需要用数组
use: {
loader: "file-loader",
// options额外的配置,⽐比如资源名称
options: {
// placeholder 占位符 [name]老资源模块的名称
// [ext]老资源模块的后缀
// https://webpack.js.org/loaders/file-loader#placeholders
name: "[name]_[hash].[ext]",
//打包后的存放位置
outputPath: "images/"
}
}
}
]
},
11.2 url-loader :处理图片base64
当图片较小的时候会把图片转换成base64编码,大于limit参数的时候还是使用file-loader进行拷贝。这样做是好处是可以直接将图片打包到bundle.js里,不用额外请求图片(省去HTTP请求)
作用: 可以弥补处理file-loader不能生成base64图片格式的缺陷,对小体积的图片比较合适,大图⽚不合适。但是,也是需要安装file-loader
安装 : yarn add url-loader -D 或者 npm install url-loader -D
案例:
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/, //是匹配图片文件后缀名称
use: {
loader: "url-loader", //是指定使用的loader和loader的配置参数
options: {
name: "[name]_[hash].[ext]",
outputPath: "images/", //图片打包到的文件目录
//小于2048B,才转换成base64 的文件打成Base64的格式,写入JS
limit: 2048 ,
publicPath:’/img’ //最终生成的CSS代码中,图片URL前缀
}
}
}
]
},
11.3 CSS预处理
(1) CSS loader配置
如果没有安装style-loader css-loader直接引入css文件就会报错:
这是因为在Webpack中没有CSS相关的Loader配置。那么接下来,来解决CSS Loader在Webpack中的配置:
- css-loader使你能够使用类似@import和url()的方法实现require()的功能
- style-loader将所有的计算后的样式加入页面中
- 而vue-style-loader是vue官方基于style-loader开发的适用于vue的样式解析,sass-loader用来解析sass/scss文件
两者结合在一起能够把样式嵌入Webpack打包后的JavaScript文件中
安装:yarn add style-loader css-loader --dev
module:{
rules:[
{
test: /\.css$/,
exclude: /node_modules/,
use: [{ loader: "style-loader"}, { loader: "css-loader" } ] //方式一
//use: ["style-loader", loader: "css-loader" ] //方式二
}
]
}
(2) 配置less环境需要安装:
安装:yarn add less less-loader --dev
module:{
rules:[
{
test: /\.css$/,
exclude: /node_modules/,
use: [{ loader: "style-loader"}, { loader: "css-loader" } ] //方式一
//use: ["style-loader", loader: "css-loader" ] //方式二
},
{
test: /\.less$/,
use: [
//方式一
{ loader: "style-loader" },
{ loader: "css-loader" },
{ loader: "less-loader" }]
//方式二
// use: ['style-loader','css-loader','less-loader']
}
]
}
(3) 配置scss环境需要安装:
sass-loader 把sass语法转换成css ,依赖node-sass模块
安装:yarn add node-sass sass-loader --dev
//注意:在项目中要less预处理和sass预处理二者选其一,不要同时混着用
loader有顺序,从右到左,从下到上
module:{
rules:[
{
test: /\.scss$/,
//方式一
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
{ loader: "sass-loader" }]
//方式二
// use: ['style-loader','css-loader','sass-loader']
}
]
}
(4) 添加PostCSS相关配置
PostCSS是一个很优秀的东西,他有很多优秀的插件,比如postcss-preset-env、Autoprefixer等。另外自己还可以根据自己需要扩展自己想要的PostCSS插件。而且在一些移动端的布局中的方案都会需要处理CSS单位之间的转换,比如Flexible(px2rem),vw-layout(px2vw)等。
可以说,现代Web开发中PostCSS相关的配置是工程体系中必不可少的。接下来,我们就看看如何在该工程体系中添加PostCSS相关的配置。要配置PostCSS相关的事项,需要:
- 安装postcss、postcss-loader
- 在Webpack配置中添加PostCSS相关的配置
- 安装自己需要的PostCSS插件
- 在postcss.config.js或.postcssrc.js添加有关于PostCSS的插件配置
先来执行第一步,安装postcss和postcss-loader:
安装:yarn add postcss-loader postcss --dev
module:{
rules:[
{
test: /\.css$/,
exclude: /node_modules/,
//use: [{ loader: "style-loader"}, { loader: "css-loader" } ]
//use: ["style-loader", loader: "css-loader" ]
use: [
{ loader: "style-loader"},
{ loader: "css-loader" },
{loader: 'postcss-loader'}
]
}
]
}
如果直接启动会报错:No PostCSS Config found....
接下来需要给项目添加PostCSS配置相关的信息。先在项目的根目录下创建postcss.config.js或.postcssrc.js文件,并添加相关的配置:
// postcss.config.js
module.exports = {
plugins: { },
}
上面的配置还没有添加任何PostCSS相关的插件。为了验证我们的配置是否成功,来安装两个PostCSS的插件,
- 添加css3前缀
postcss-preset-env 将现代CSS转换成浏览器能理解的东西
安装postcss-preset-env,无需再安装autoprefixer,由于postcss-preset-env已经内置了相关功能。
安装:yarn add postcss-preset-env --dev
并把这两插件的相关配置中添加到postcss.config.js中:
module.exports = {
plugins: {
'postcss-preset-env': {
autoprefixer: { flexbox: 'no-2009'}, //可去掉
stage: 3
},
}
}
如果安装了自动补全插件 autoprefixer, yarn add autoprefixer --dev 也可以通过这个配置来生效
module.exports = {
plugins: {
'autoprefixer': { }
}
}
在样式文件中添加一些样式代码来验证是否生效了:
.box {
width: 50px;
height: 30px;
background: url(../images/ABC.jpg);
transform:rotate(30deg);
}
如果生效,会是这样的
.box
{
transform:rotate(7deg);
-ms-transform:rotate(7deg); /* IE 9 */
-moz-transform:rotate(7deg); /* Firefox */
-webkit-transform:rotate(7deg); /* Safari 和 Chrome */
-o-transform:rotate(7deg); /* Opera */
}
如果没有说明没有生效, 需要配置
需要在package.json中添加browserslist相关的配置信息:
package.json
"browserslist": [
"last 10 Chrome versions",
"last 5 Firefox versions",
"Safari >= 6",
"ie> 8"
],
- 单位转换 px - rem
把项目中px单位转化为 rem,实现响应式,
安装:
yarn add postcss-pxtorem -D
module.exports = {
plugins:{
'postcss-preset-env': {
autoprefixer:{
flexbox: 'no-2009'
},
stage: 3
},
'postcss-pxtorem':{
rootValue:10,
minPixelValue:2,
propWhiteList: []
}
}
}
(5) css抽离 后面插件讲
11.4 使用ejs模块化导入
11.4.1 安装:
yarn add ejs ejs-loader
11.4.2 配置:
{
test:/\.ejs$/,
use:{
loader:'ejs-loader',
options:{
esModule:false
}
}
},
注意:
options:{ esModule:false }
必须要加,不然会报,让ejs模块导入不支持esModule,支持commonJS的语法
然后再templates目录下 mkdir modules & cd modules & touch header.ejs
并添加相关代码
再在模板文件 index.ejs里 通过 下列语法导入
<%= require(./modules/header.ejs)() %>
十二、增加babel⽀持
12.1 Babel 是什么?
Babel 是一个 JavaScript 编译器
Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情:
- 语法转换
- 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块)
- 源码转换 (codemods)
@babel/preset-env + @babel/polyfill 可以完全实现 ES 基础语法的转译 + 新 API 的转译,这是配置 Babel 转码的第一种方式。
12.1 babel7 的一些变化
preset 的变更: 淘汰 es201x,删除 stage-x,推荐 env
如果你还在使用 es201x,官方建议使用 env 进行替换。淘汰并不是删除,只是不推荐使用。 但 stage-x 是直接被删了,也就是说在 babel7 中使用 es201X 是会报错的。
包名称变化
babel 7 的一个重大变化,把所有 babel-* 重命名为 @babel/*,
例如:
babel-cli —> @babel/cli。
babel-preset-env —> @babel/preset-env
Babel附带了一个内置的CLI(@babel/cli),可用于从命令行编译文件.
你可以在您的机器上全局安装@babel/cli,但最好进行局部安装。
- 同一台机器上的不同项目可以依赖于不同版本的Babel,允许您单独更新它们
- 没有对工作环境的隐式依赖性会使项目更易于移植和设置
yarn add @babel/core @babel/cli -D
12.2 安装
安装:yarn add @babel/core babel-loader @babel/preset-env -D
其中preset是babel的配置,babel-loader在webpack中解析babel中会用到,polyfill也是babel的一个依赖在webpack中解析babel中会用到
12.3 babel-loader
babel-loader是webpack 与 babel的通信桥梁,在webpack中解析babel中会用到,不会做把es6转成es5的工作,这部分工作需要用到 @babel/preset-env来做
@babel/preset-env里包含了es6转es5的转换规则
babel-preset-es2015 已经被babel-preset-env替代,现在已经是@babel/preset-env
@babel/preset-env 这个比较强大, 是一个面向未来得库, 即使是es7,8,9出来以后,都可以帮忙处理
12.4 Babel7 中 @babel/preset-env 的使用
是一系列插件的集合,包含了我们在babel6中常用的es2015,es2016, es2017等最新的语法转化插件,允许我们使用最新的js语法,比如 let,const,箭头函数等等,但不包括stage-x阶段的插件。
preset 即“预制套件”,包含了各种可能用到的转译工具
基本的语法转换,需要添加预设 @babel/preset-env
安装依赖包
yarn add babel-loader @babel/core @babel/preset-env -D
配置如下:
{
test:/\.(jsx|js)$/,
use:{
loader:'babel-loader',
},
exclude:/node_modules/
}
.babelrc 配置:
{
"presets":["@babel/preset-env",{
"targets": { }
}]
}
注意:浏览器兼容的配置
12.5 browserslist的作用
我们需要知道的是制定个targets的browsers时使用的是 browserslist ,有3个地方可以进行配置:
- .babelrc 文件 的targets
- package.json文件的browserslist
- .browserslistrc
我们可以在 .babelrc 文件、package.json文件、browserslist中指定浏览器版本选项,优先级规则是 .babelrc文件定义了则会忽略 browserslist、.babelrc 没有定义则会搜索 browserslist 和 package.json 两者应该只定义一个,否则会报错。
至于要兼容哪些浏览器,可以自行配置,这里可以提供2个配置
12.5.1 使用方法
(1) .babelrc
根据浏览器的市场份额来定
{
"presets":["@babel/preset-env",{
"targets": {
"browsers": [ "> 1%", "last 5 versions", "ie >= 8" ]
}
}]
}
(2) package.json (推荐)
在package.json文件添加browserslist
"browserslist": [
last 5 version
"ie>= 8"
],
(3) .browserslistrc 在项目根目录新建 .browserslistrc ,添加以下兼容哪些浏览器的代码
# Browsers that we support
last 5 version
> 1%
ie >= 8
如果你想知道配置语句的查询结果可以使用[online demo] (browserl.ist/) 你就可以看到最新的各个浏览器版本
12.5.2 支持的插件
Browserslist这个东西单独是没用的,(补充: 在vue官方脚手架中,browserslist字段会被 @babel/preset-env 和 Autoprefixer 用来确定需要转译的 JavaScript 特性和需要添加的 CSS 浏览器前缀)下面的搭配的工具列表:
- Autoprefixer
- Babel
- postcss-preset-env
- eslint-plugin-compat
- stylelint-no-unsupported-browser-features
- postcss-normalize
或者npx browserslist
Browserslist 默认值:
> 0.5%,
last 2 versions,
Firefox ESR,
not dead
12.5.3 最佳实践
最好不要这么写
"last 10 Chrome versions",
"last 5 Firefox versions",
"Safari >= 6",
因为有很多浏览器厂商 , 不能一一全写, 从市场份额来分就行
- last 2 Chrome versions 这种配置最好当你只想在对应的一个浏览器下开发webapp,生产情况下最好不要,毕竟其他浏览器也有市场份额
- 如果你想自己配置,我建议你结合 last 1 version, not dead with > 0.2% (or > 1% in US, > 1% in my stats. last n version会增加很过过时的浏览器并且没有增加流行的旧版本浏览器。> 0.2%看起来更加合适(根据份额来定)
- 不要仅因为您不了解浏览器而删除浏览器。Opera Mini在非洲拥有1亿用户,它在全球市场上比微软边缘更受欢迎。中文QQ浏览器的市场份额比Firefox和桌面Safari都要多。(第一次知道,qq还是强啊,但是360呢..)
测试代码:
//index.js
const arr = [new Promise(() => {}), new Promise(() => {})];
arr.map(item => { console.log(item); });
var isCol = ['11','22'].includes('11');
console.log(isCol);
在ie10会报错
通过上面的几步 还不够,includes Promise等一些还没有转换过来,这时候需要借助@babel/polyfill,把es的新特性都装进来,来弥补低版本浏览器中缺失的特性
12.5 @babel/polyfill (1)
也就是babel6 中的 babel-polyfill , 使用 preset-env 能将最新的语法转换为ecmascript5的写法,当我们需要使用新增的全局函数(比如promise, Array.from)和实例方法(比如 Array.prototype.includes )时就需要引入 polyfill ,一般用法 在 index.js文件的最上层引入,或是在打包文件的entry 入口处引入,这样做的缺点是会全局引入整个 polyfill包,比如promise 会全局引入,污染全局环境。
以全局变量的方式注入进来的。windows.Promise,它会造成全局对象的污染
安装: yarn add @babel/polyfill --dev
Webpack.config.js配置:
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
}
}
index.js 顶部引入
import "@babel/polyfill";
会发现打包的体积大了很多,这是因为polyfill默认会把所有特性注入进来,假如我想我用到的es6+,才会注入,没用到的不注入,从而减少打包的体积,可不可以呢
当然可以
修改Webpack.config.js
{
test:/\.(jsx|js)$/,
use:{
loader:'babel-loader', //webpack 与 babel的通信桥梁
// options:{
// presets:[ "@babel/preset-env"] // 把es6转成es5的工作
// }
// },
exclude:/node_modules/
}
babelrc文件: 项目根目录, 新建 .babelrc文件
{
"presets": [
[
// 把es6转成es5的工作
"@babel/preset-env", {
"useBuiltIns": "usage", //按需注入
"targets": {
"browsers": [ "> 1%", "last 5 versions", "ie >= 8" ]
}
}
]
]
}
进一步测试,这个时候ie9是可以运行, 但是IE8 提示
原因:在IE8下只能对DOM对象使用, 如果对原生对象使用Object.defineProtry()会报错 参考:www.cnblogs.com/zhangrunhao…
12.6 @babel/polyfill 和 @babel/preset-env 的关系
@babel/preset-env 中与 @babel/polyfill 的相关参数有 targets 和 useBuiltIns 两个
-
targets: 支持的目标浏览器的列表
-
useBuiltIns: 选项是 babel 7 的新功能,这个选项告诉 babel 如何配置 @babel/polyfill , 它有三个参数可以使用:
-
entry: 需要在 webpack 的入口文件里 import "@babel/polyfill" 一次。
-
usage: 不需要 import "@babel/polyfill" ,全自动检测,但是要安装 @babel/polyfill 。
-
false: 这也是默认值 , 使用这个值时不引入 polyfill
使用webpack,有多种方法可以包含polyfill
-
当与babel-preset-env一起使用时,如果在.babelrc中指定了useBuiltIns: 'usage',那么在webpack.config.js配置的entry处或源代码中都不要包含babel-polyfill。注意,babel-polyfill仍然需要安装。
-
如果在.babelrc中指定了useBuiltIns: 'entry',那么在应用程序入口点的顶部包括babel-polyfill,方法是按照上面讨论的require或import。
-
如果在.babelrc中未指定useBuiltIns键,或者使用useBuiltIns: false设置了该键,则直接将babel-polyfill添加到webpack.config.js中的entry中。
{
"presets": [
[
"@babel/preset-env", {
"useBuiltIns": "usage", //entry,false
"targets": {
"browsers": [ "> 1%", "last 5 versions", "ie >= 8" ]
}
}
]
]
}
在引入@babel/preset-env(一个帮你配置babel的preset,根据配置的目标环境自动采用需要的babel插件)配置babel中的useBuiltIns: 'usage'时,编译出现又出现这个警告提示
问题解释:警告在使用useBuiltIns选项时,需要声明core-js的版本
解决办法: 1)方法一:安装yarn add core-js@2 2)方法二:在 .babelrc 文件中添加corjs:”2”,申明corejs的版本
{
"presets": [
[
"@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": "2", // 声明corejs版本
"targets": {
"browsers": [ "> 1%", "last 5 versions", "ie >= 8" ]
}
}
]
]
}
core-js 支持两个版本, 2 或 3, 很多新特性已经不会加入到 2 里面了, 比如: flat 等等最新的方法, 2 这个版本里面都是没有的, 所以建议大家用3
完美解决问题!
总结:在业务项目中需要用到polyfill时, 可以使用和 @babel/preset-env 的 targets 和 useBuiltIns: usage来根据目标浏览器的支持情况,按需引入用到的 polyfill 文件。
12.6 @babel/runtime(2)
这种方式会借助 helper function 来实现特性的兼容, 并且利用 @babel/plugin-transform-runtime 插件还能以沙箱垫片的方式防止污染全局, 并抽离公共的 helper function , 以节省代码的冗余
也就是说 @babel/runtime 是一个核心, 一种实现方式, 而 @babel/plugin-transform-runtime 就是一个管家, 负责更好的重复使用 @babel/runtime
@babel/plugin-transform-runtime 插件也有一个 corejs 参数需要填写
版本2 不支持内置对象 , 但自从Babel 7.4.0 之后,拥有了 @babel/runtime-corejs3 , 我们可以放心使用 corejs: 3 对实例方法做支持
当我们开发的是组件库,工具库这些场景的时候,polyfill就不适合了,因为polyfill是注入到全局变量,window下 的,会污染全局环境,所以推荐闭包方式: @babel/plugin-transform-runtime 参考:www.cnblogs.com/htoooth/p/9…
它不会造成全局污染
yarn add @babel/plugin-transform-runtime --dev
yarn add @babel/runtime --save //注意这里安装到dependences的依赖包
如果报错,还需要安装 @babel/runtime-corejs3
yarn add @babel/runtime-corejs3 -D
12.6.1. 怎么使用?
先注释掉index.js里的polyfill
修改配置文件:注释掉之前的presets,添加plugins
options: {
"presets": [
[
"@babel/preset-env", {
// "useBuiltIns": "usage", //按需注入
// "corejs": "2", // 声明corejs版本
"targets": {
"browsers": [ "> 1%", "last 5 versions", "ie >= 8" ]
}
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 3,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
我们看到使用 @babel/plugin-transform-runtime 编译后的代码和之前的 @babel/preset-env 编译结果大不一样了, 它使用了帮助函数, 并且赋予了别名 , 抽出为公共方法, 实现复用。 比如它用了 _Promise 代替了 new Promise , 从而避免了创建全局对象
12.7 上面两种处理pollyfill方式一起用会怎么样
12.7.1 useage 和 @babel/runtime
useage 和 @babel/runtime 同时使用的情况下比较智能, 并没有引入重复的 polyfill
12.7.2 entry 和 @babel/runtime
跟 useage 的情况不一样, entry 模式下, 在经过 @babel/runtime 处理后不但有了各种帮助函数还引入了许多polyfill, 这就会导致打包体积无情的增大
个人分析: entry 模式下遭遇到入口的 import "core-js" 及就立即替换为当前目标浏览器下所需的所有 polyfill, 所以也就跟 @babel/runtime 互不冲突了, 导致了重复引入代码的问题, 所以这两种方式千万不要一起使用, 二选一即可
方式二: 下面的2种情况都不推荐
// 情况一:在webpack中引用
module.exports = {
entry: ["babel-polyfill", "./app/js"] //不推荐
};
// 情况二:在js入口顶部引入
import "babel-polyfill"; //不推荐
https://babeljs.io/docs/en/6.26.3/babel-polyfill
12.7.3 总结:
- @babel/preset-env 拥有根据 useBuiltIns 参数的多种polyfill实现,优点是覆盖面比较全(entry), 缺点是会污染全局, 推荐在业务项目中使用
- entry 的覆盖面积全, 但是打包体积自然就大,
- useage 可以按需引入 polyfill, 打包体积就小, 但如果打包忽略node_modules 时如果第三方包未转译则会出现兼容问题
- @babel/runtime 在 babel 7.4 之后大放异彩, 利用 corejs 3 也实现了各种内置对象的支持, 并且依靠 @babel/plugin-transform-runtime 的能力,沙箱垫片和代码复用, 避免帮助函数重复 inject 过多的问题, 该方式的优点是不会污染全局, 适合在类库开发中使用 上面 1, 2 两种方式取其一即可, 同时使用没有意义, 还可能造成重复的 polyfill 文件
十三、常见插件Plugins
13.1 重点插件plugin介绍
插件与模块解析的功能不一样,模块解析是为了导入非es5格式js或其它资源类型文件,定制了一些loader。插件是对最后的打包文件进行处理的,也可以理解loader是为打包前做准备,plugin是打包再进行处理
官方插件的使用步骤(内置插件2步)
① 配置文件中导入XxxxPlugin, const wp= require(‘webpack’)
② 在plugins这个数组中加入一个插件实例,new wp.XxxxPlugin({对象})
第三方插件的使用步骤(第3方3步,多一次安装)
① 安装(第三方插件要安装) 根目录>npm i -D XxxxPlugin
② 配置文件中导入插件 const XxxxPlugin = require('xxxx-plugin')
③ 在plugins这个数组中加入一个插件实例,new XxxxPlugin({对象})
官方插件有
可以在配置中打印查看
const webpack = require('webpack')
console.log(webpack ) //这里可以看到哪些是webpack内置的插件
待讲插件清单
01: webpack.BannerPlugin 加注释 02: terser-webpack-plugin 代码缩小 03: html-webpack-plugin 生成html页 04: 以前的extract-text-webpack-plugin,mini-css-extract-plugin 提取css等 05.DefinePlugin //定义一个全局常量,如new wp.DefinePlugin({BJ: JSON.stringify('北京'),}),在待打包的js文件中可以直接使用,如在./src/main.js中console.log('我是在配置文件中定义的'+BJ) 06.Dllplugins
13.2 BannerPlugin
作用: 在打包的文件中添加注释 //这是webpack内置的插件,所以不用require导入,但是对于第三方插件要先导入
plugins: [
new webpack.BannerPlugin({
banner: '永远要记得,成功的决心远胜于任何东西'
})
]
13.3 打包复制
作用:在webpack中拷贝文件和文件夹
安装: yarn add copy-webpack-plugin --dev
from 定义要拷贝的源文件 from:__dirname+'/src/components'
to 定义要拷贝到的目标文件夹 to: __dirname+'/dist'
toType file 或者 dir 可选,默认是文件
force 强制覆盖前面的插件 可选,默认是文件
context 可选,默认base context可用specific context
flatten 只拷贝指定的文件 可以用模糊匹配
ignore 忽略拷贝指定的文件 可以模糊匹配
插件引入和配置:
const CopyWebpackPlugin = require('copy-webpack-plugin');
new CopyWebpackPlugin({
patterns:[
{
from:path.resolve(__dirname,'../static'),
to:dist_path+'/static'
}
]
})
13.4 js压缩插件
作用:压缩js 代码,减小体积 这个插件不是内置的,要先安装
不推荐使用 webpack-parallel-uglify-plugin 项目基础处于没人保护的阶段,issue 没人处置,pr没人兼并。
推荐运用 terser-webpack-plugin
terser-webpack-plugin 是一个运用 terser 紧缩js的webpack 插件。 紧缩是最耗时的一个步骤,如果是你是在webpack 4 中,只需几行代码,即可加快你的构建速率。
安装:因为是第三方的 , 所以需要安装 , github.com/webpack-con…
yarn add terser-webpack-plugin -D
用法
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: 4, //多线程
}),
],
},
};
13.5 抽离css样式
抽离css的目的是防止将样式打包在js中方便缓存静态资源
插件一: extract-text-webpack-plugin(不推荐) yarn add extract-text-webpack-plugin^4.0.0-beta.0 --dev 注意:如果用这个 需要 指定版本:"extract-text-webpack-plugin": "^4.0.0-beta.0"
插件二:mini-css-extract-plugin(推荐)
13.5.1 extract-text-webpack-plugin 演示:
module配置:
const ExtractTextPlugin = require("extract-text-webpack-plugin");
//module部分
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins插件部分
plugins: [
new ExtractTextPlugin("styles.css"),
]
}
use:指需要什么样的loader去编译文件,这里由于源文件是.css所以选择css-loader fallback:编译后用什么loader来提取css文件
13.5.2 mini-css-extract-plugin 演示:
相比extract-text-webpack-plugin:
- 异步加载
- 没有重复的编译(性能)
- 更容易使用
- 特定于CSS
安装: yarn add mini-css-extract-plugin -D
抽出 css :
module部分
const MiniCssExtractPlugin= require("mini-css-extract-plugin");
//........
{
test: /\.less$/,
exclude: /node_modules/,
use: [
{
loader:MiniCssExtractPlugin.loader //代替style-loader
},
// { loader: "style-loader"},
{ loader: "css-loader" },
{ loader: 'postcss-loader'},
{ loader: "less-loader" }
]
},
plugins插件部分
// 这里的配置和webpackOptions.output中的配置相似 // 即可以通过在名字前加路径,来决定打包后的文件存在的路径
new MiniCssExtractPlugin({
filename:'styles/[name].[contenthash:8].css',
chunkFilename:'style/[id].[contenthash:8].css' //按需引入生成css
})
[id]和[name]在webpack中被称做placeholder 用来在webpack构建之后动态得替换内容的(本质上是正则替换)。 chunkFilename是构建应用的时候生成的。
13.6 压缩css: optimize-css-assets-webpack-plugin
- 生产环境的配置,默认开启tree-shaking和js代码压缩;
- 通过optimize-css-assets-webpack-plugin插件可以对css进行压缩,与此同时,必须指定js压缩插件(例子中使用terser-webpack-plugin插件),否则webpack不再对js文件进行压缩; 设置optimization.splitChunks.cacheGroups,可以将css代码块提取到单独的文件中。
安装:yarn add optimize-css-assets-webpack-plugin -D
引入插件与配置:
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
optimization:{
minimize:true,
minimizer:[
new TerserPlugin({
cache:true, //释放要缓存
parallel: 4, //多线程,并行打包
}),
new OptimizeCSSAssetsPlugin({})
]
},
})
总结
-
不同环境下的打包,如果出现图片显示不了时(特别是css中的图片),请检查publicPath的配置。
-
mode: 'production'会开启tree-shaking和js代码压缩,但配置optimization. minimizer会使默认的压缩功能失效。所以,指定css压缩插件的同时,务必指定js的压缩插件。
-
mini-css-extract-plugin插件,可以把css代码打包到单独的css文件,且可以设置存放路径(通过设置插件的filename和chunkFilename)。
splitChunks就算你什么配置都不做它也是生效的,源于webpack有一个默认配置,这也符合webpack4的开箱即用的特性,它的默认配置如
13.7 生成json文件的列表索引插件
安装: yarn add assets-webpack-plugin --dev
var AssetsPlugin = require('assets-webpack-plugin');
//生成json文件的列表索引插件
new AssetsPlugin({
filename: 'assets-resources.json',
fullPath: false,
includeManifest: 'manifest',
prettyPrint: true,
update: true,
path: buildPath,
metadata: {version: 123}
});
13.8 sourceMap
选择一种 source map 格式来增强调试过程。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。
www.webpackjs.com/configurati…
在dev模式中,默认开启,关闭的话 可以在配置文件⾥ devtool:"none"
开发环境推荐 devtool:"cheap-module-eval-source-map"
线上环境可以不开启:如果要看到一些错误信息,推荐; devtool:"cheap-module-source-map"
十四、 置别名快捷方式 resolve
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'src': srcPath,
'styles': srcPath+'/styles',
'images':srcPath+'/images',
'config':path.resolve(srcPath,'js/config.js')
}
}
resolve.alias :设置别名 resolve.enforceExtension :默认是false 如果是 true,将不允许无扩展名(extension-less)文件。如果启用此选项,只有 require('./foo.js') 能够正常工作。 resolve.extensions 自动解析确定的扩展。
十五、 设置环境变量 通过script
很久以前这样用过:
script:{
"start": "export NODE_ENV='development' && node app.js"
// 在Mac和Linux上使用export, 在windows上export要换成set
}
目前推荐 cross-env
15.1 cross-env
安装:yarn add cross-env --dev
注意:cross-env scene=dev cross-env scene=prod 后面不能添加&
"scripts": {
"dev": "cross-env scene=dev webpack-dev-server --config webpack.config.js",
"build": "cross-env scene=prod webpack --mode production --config
webpack.config.js"
},
如果有打包之前删除 打包的功能,如下,注意顺序:
"scripts": {
"test": " rm -rf dist & cross-env scene=test webpack --config ./build/webpack.prod.js ",
"prod": " rimraf dist && cross-env scene=prod webpack --config ./build/webpack.prod.js ",
"dev": "cross-env scene=dev webpack-dev-server --config ./build/webpack.dev.js ",
"dev1": "webpack-dev-server --config test.js --mode development",
"delete": "rimraf dist1"
},
需求: 根据不同的开发环境,来设置一些变量
console.log(process.env.scene);
var isProduction = (process.env.scene=='prod');
十六、 DefinePlugin 设置 全局常量
DefinePlugin 允许创建一个在编译时可以配置的全局常量 在配置文件里获取变量并把值设置,DefinePlugin是webpack自带的插件,无法安装
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.DEBUG': JSON.stringify(process.env.DEBUG)
})
pluginsAll.push(new webpack.DefinePlugin({
'sceneParam': JSON.stringify(process.env.scene),
'laney':JSON.stringify('laney'),
'test':'"kkkkk"'
}));
然后在项目中,根据不同的环境来调用不同的样式 ,异步加载模块的方式有2种, es6的模式,如下
switch(sceneParam){
case 'prod':
console.log('11111');
break;
case 'dev':
console.log('222222');
break;
}
如果这么使用会报错: import 和 export 只能在顶级用,不能在代码块中用。否则会报 'import' and 'export' may only appear at the top level。
使用commonJS异步的模块加载
这个算是可以减小模块的体积吧,在一定程度上也是为用户考虑的,使用require.ensure来设置哪些模块需要异步加载,webpack会将它打包到一个独立的chunk中, 在某个时刻(比如用户点击了查看)才异步地加载这个模块来执行
$('.bg-input').click(() => {
console.log('clicked, loading async.js')
require.ensure([], require => {
require('./components/async2').log();
require('./components/async1').log();
console.log('loading async.js done');
});
});
十七、 EnvironmentPlugin
EnvironmentPlugin 是一个通过 DefinePlugin 来设置 process.env 环境变量的快捷方式。 用法: new webpack.EnvironmentPlugin(['NODE_ENV', 'DEBUG']) 不同于 DefinePlugin,默认值将被 EnvironmentPlugin 执行 JSON.stringify。 上面的写法和下面这样使用 DefinePlugin 的效果相同:
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
// 除非有定义 process.env.NODE_ENV,否则就使用 'development'
DEBUG: false})
在页面中访问的方式:
if (process.env.NODE_ENV === 'production') {
console.log('Welcome to production');
}
if (process.env.DEBUG) {
console.log('Debugging output');
}
十八、 配置全局可引用的配置文件 webpack.ProvidePlugin
在src/js里添加一个 配置文件,或者全局文件configCommon,然后在build/webpack.common.js 的插件里修改如下:
plugins.push(new webpack.ProvidePlugin({
// config:path.resolve(srcPath,'js/config.js') ////导出的方式为export { }
// config:'config'
// config:['config','default'],
configCommon:[path.resolve(srcPath,'js/config.js'),'default'], ////导出的方式为export default{ }
// $:'jquery',
// jQuery:'jquery'
}));
重启项目后,在页面中就可以在项目文件中直接用 configCommon,包括里面的方法和 各种参数,非常爽.
18.1 使用场景微服务架构
方式一:webpack.ProvidePlugin 为了保证一个服务坍塌了, 不影响别的服务,需要有多个服务,多个域名, 如果是项目中可能有多个接口的域名, 不同的环境, 这些 接口的 域名都不一样, 这个时候 ,需要根据 开发 环境, 测试环境,以及线上分别配置,比如:
commonFun.environment = {
// 开发环境:
dev:{
service:'http://localhost:8081',
setting:'http://localhost:8082',
manage:'http://localhost:8083'
},
// 测试环境:
test:{
service:'http://192.168.0.1:8081',
setting:'http://192.168.0.1:8082',
manage:'http://192.168.0.1:8083'
},
// 线上环境:
prod:{
service:'http://test1.ruanmou.com',
setting:'http://test2.ruanmou.com',
manage:'http://test3.ruanmou.com'
}
}
如何去根据不同的环境调用这些 api的域呢, 第一种方法可以用webpack.ProvidePlugin ,在src\js\config.js加上以上配置,在其他页面就可以直接通过这个configCommon.environment[sceneParam] 获取到当前所有接口需要的域
18.2 方式二:在根目录添加配置,根据环境写入到index.html
但是这个 方法 是每次都需要重新打包才可以生效, 有没有有一种方法可以 修改配置后, 不需要打包的呢, 那这个时候可以 把配置文件在 index.html里直接通过script引入相应环境的配置。 在根目录添加 service.local.js ,用于本地开发 配置如下:
window.serviceIpConfig = {
dev:{
Product:'http://10.0.1.28:888',//商品服务
Order:'http://10.0.1.28:888',//订单服务
Member:'http://10.0.1.28:999',//会员服务
Auth:'http://10.0.1.28:222',//授权服务
},
debug:{
Product:'http://127.0.0.1:888',//商品服务
Order:'http://127.0.0.1:888',//订单服务
Member:'http://127.0.0.1:999',//会员服务
Auth:'http://127.0.0.1:222',//授权服务
}
}
在根目录添加service.online.js ,用于线上开发, 这个文件通过复制的方式把文件复制到打包的static的文进件 在wenpack.common.js里添加
var WEBPACK_ENV = process.env.scene.trim();
在通过模板htmlWebpackPlugin打包的时候,选择不同的模板,本地开发用template/index-dev.html.,开发的时候用template/index-prod.html, 分别写入不同的配置文件就行, 就达到了 即使修改 接口得ip也可以及时生效, 不需要重新打包的需求了。
十九、 Tree-Shaking和ES6 module
动态与静态
CommonJS与ES6 Module最本质的区别在于CommonJS对模块依赖的解决是“动态的”而ES6 Module是“静态的”。在这里“动态的”含义是,模块依赖关系的建立发生在代码运行阶段;而“静态”则是模块依赖关系的建立发生在代码编译阶段
19.1 ES6 module 静态结构
当前的JavaScript模块格式具有动态结构:导入和导出的内容可以在运行时(runtime)更改。 ES6引入自己的模块格式的一个原因是启用静态结构。静态结构意味着您可以在编译时确定导入和导出(静态) --- 您只需要查看源代码,而不必执行它。
ES6在语法上强制执行:您只能在顶层导入和导出(而不嵌套在条件语句中)。
19.2 动态的CommonJS模块
如下代码,只有运行代码的时候才能知道导入的内容;
B文件
//calculator.js
module.exports={name:"calculator"};
A文件
//index.js
const name=require('./calculator.js').name;
当模块A加载模块B时,会执行B中的代码,并将其module.exports对象作为require函数的返回值进行返回。并且require的模块路径可以动态指定,并支持传入一个表达式或者一个if进行判断是否加载模块。因此可以看出,在commonJS模块被执行前,并没有办法确定明确的依赖关系,模块的导入、导出发生在代码的运行阶段。
19.3 ES6 模块 export import
//calculator.js
export const name='calculator';
//index.js
import {name} from './calculator.js';
ES6 Module的导入、导出语句都是声明式的,它不支持导入的路径是一个表达式,并且导入、导出语句必须位于模块的顶层作用域不能放到if语句中。因此ES6 Module是一个静态模块结构。
ES6 Module相比与CommonJS的优点:
- 在捆绑期间消除死代码,优化打包之后的代码体积(Tree-Shaking)
- 模块变量类型检查(静态的知道哪些变量在模块内的哪些位置可见)。
- 更快查找到入口 (commonjs必须执行才能查找,很慢,因为是动态的)
- 为类型做好准备(静态类型检查加强)
19.4 什么是Tree-Shaking?
Tree-Shaking是一个术语,通常用于描述移除Javascript上下文中的未引用的代码(dead-code)。它依赖于ES6 module语法的静态结构特性,如 import 和 export ,用来检测代码模块是否被导出、导入,且被Javascript文件使用。使用模块打包(webpack,rollup)将多个Javascript文件打包为单文件时自动删除未引用的代码。对于发布线上代码起到优化作用。这个术语和概念实际是由ES2015模块打包工具rollup普及起来的。
tree-shaking更关注于无用模块的消除,消除那些引用了但并没有被使用的模块。DCE 消灭不可能执行的代码。
在webpack 2正式版内置支持ES6模块和未使用模块检测能力。新的webpack 4正式版本扩展了此检测能力,通过 package.json 的"sideEffects"属性作为标记,向compiler提供提示,表明项目中的哪些文件是" pure(纯 ES2015 模块) ",由此可以安全的删除文件中未使用的部分。
19.5 使用Tree-Shaking 压缩代码(webpack 4)
webapck.config.js
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'budle.js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
usedExports: true
}
}
将文件标记为 side-effect-free(无副作用):提示 webpack compiler 哪些代码是“纯粹部分”,通过 package.json 的 "sideEffects"属性,来实现这种方式
{
"name": "webpack-4-demo",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack-dev-server --open --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
},
"devDependencies": {
},
"sideEffects": false
}
19.6 结论
在webpack 4版本中,直接对生产环境配置mode:'production',即可启用tree shaking,并将代码压缩, 然后在package.json文件加上"sideEffects": false,(注意,所有导入文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader 并 import 一个 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除:)用来提示webpack compiler找出哪些代码是未被引用的,然后删除掉。
想要使用tree shaking 必须注意如下几点:
- 使用es6 模块语法(import 和 export)
- 确保没有compiler将ES6模块语法转换为CommonJS模块(Babel preset 中 @babel/preset-env 的默认值是auto,将module 设置为false将不会转换模块)
- 在项目 package.json 文件中,添加一个"sideEffects"属性
- 通过将 mode 选项设置为 production,启用 minification(代码压缩) 和 tree shaking。 你可以将应用程序想象成一棵树,绿色表示实际用到的 source code(源码) 和 library(库),是树上活的树叶。灰色表示未引用代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。
{
"name": "webpack-demo",
//@babel/polyfill和css文件不使用tree shaking
"sideEffects": [
"@babel/polyfill",
"*.css"
],
"version": "1.0.0",
"description": "",
}
二十、代码分割 code Splitting
import _ from "lodash";
console.log(_.join(['a','b','c','****']))
假如我们引入一个第三方的工具库,体积为1mb,而我们的业务逻辑代码也有1mb,那么打包出来的体积大小会在2mb
导致问题:
- 体积大,加载时间长
- 业务逻辑会变化,第三方工具库不会,所以业务逻辑一变更,第三方工具库也要跟着变。
引入代码分割的概念
lodash.js
import _ from "lodash";
window._ = _;
index.js
//注释掉lodash引用 //import _ from "lodash";
console.log(_.join(['a','b','c','****']))
webpack.config.js
entry: {
lodash: "./lodash.js",
index: "./index.js"
},
//指定打包后的资源位置
output: {
path: path.resolve(__dirname, "./build"),
filename: "[name].js"
}
它的默认配置如下:
optimization: {
splitChunks: {
chunks: 'async',//对同步,异步,所有的模块有效
minSize: 30000,//当模块大于30kb
maxSize: 0,//对模块进行二次分割时使用,不推荐使用
minChunks: 1,//打包生成的chunk文件最少有几个chunk引用了这个模块
maxAsyncRequests: 5,//模块请求5次
maxInitialRequests: 3,//入口文件同步请求3次
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10//优先级 数字越大,优先级越高
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
使用下面配置即可:
optimization:{
//帮我们自动做代码分割
splitChunks:{
chunks:"all",//默认是支持异步,我们使用all ,只设置这一个就可以工作
//chunks:"async",//默认是支持异
}
}
二十一、代码按需加载
测试代码: //index.js
document.addEventListener("click", () => {
const element = document.createElement("div");
element.innerHTML = "welcome to webpack4.x";
document.body.appendChild(element);
});
通过控制台看看代码利用率
把里面异步代码抽离出来 //index.js
document.addEventListener("click", () => {
import("./click.js").then(({ default: func }) => {
//需要用到 npm install --save-dev @babel/plugin-syntax-dynamic-import
func();
});
});
//click.js
function handleClick() {
const element = document.createElement("div");
element.innerHTML = "welcome to webpack4.x";
document.body.appendChild(element);
}
export default handleClick;
以上情况是在点击 按钮以后才会动态去加载相应的js文件, 但是有可能你在点击的时候,会出现用户用等待的情况 而出现延时, 可否让浏览器把 主要的代码加载完毕后, 在空余的时间 去加载异步点击的代码呢?
使用预先拉取和预先加载提升性能
Webpack 4.6.0为我们提供了预先拉取(prefetching)和预先加载(preloading)的功能。使用这些声明可以修改浏览器处理异步chunk的方式。
- 预先拉取
使用预先拉取,你表示该模块可能以后会用到。浏览器会在空闲时间下载该模块,且下载是发生在父级chunk加载完成之后。
import(
`./utilities/divide`
/* webpackPrefetch: true */
/* webpackChunkName: "utilities" */
)
以上的导入会让被添加至页面的头部。因此浏览器会在空闲时间预先拉取该文件。
- prefetch概念
为了帮助其它浏览器对某些域名进行预解析,你可以在页面的html标签中添加dns-prefetch告诉浏览器对指定域名预解析,如下:
<link rel="dns-prefetch" href="//domain.com">
DNS Prefetch,即DNS预获取,是前端优化的一部分。一般来说,在前端优化中与 DNS 有关的有两点: 一个是减少DNS的请求次数, 另一个就是进行DNS预获取