回顾webpack和webpack-dev-server
webpack | webpack-dev-server |
---|---|
适用于webpack打包机制 | 适用于webpack打包机制 |
输出真实的文件一般是dist | 输出的文件只存在于内存中,不输出真实的文件 |
文件修改要重新打包 | 文件修改保存会自动更新 |
按生产环境需求进行打包生产环境代码 | 按开发环境需求进行打包开发环境代码 |
案例
案例一
babel使用
- Babel 是一个 JavaScript 编译器,可以把ES6的语法转为兼容浏览器的ES5语法
webpack里使用babel
// 已经在项目里安装了webpack的情况下
npm install -D babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/runtime
// 添加.babelrc
{
"presets": [
"@babel/env"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-runtime"
]
}
// 添加js的loader
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
extends: join(__dirname, '.babelrc'),
cacheDirectory: true,
},
},
exclude: /node_modules/,
}
// second.js写一个es6的语法
setTimeout(() => {
console.log(1);
}, 1000);
案例二
开发环境和生产环境
思路:
开发环境和生产环境有很多通用的代码,可以将通用的代码提取出来,例如loaders,output等,所以可以形成三个文件,webpack.prod.config.js、webpack.dev.config.js、webpack.common.config.js,分离出来如何将他们分别合并,这时候就需要使用到webpack-merge,进行配置合并
webpack-merge
用法:
// 安装命令
npm i webpack-merge -D
// 开发和生产环境需要引入
var { merge } = require('webpack-merge');
var commonConfig = require('./webpack.common.config');
// webpack开发环境合并
merge(commonConfig, devConfig)
// webpack生产环境合并
merge(commonConfig, prodConfig)
// 配置package.json脚本命令,分别读取webpack的文件
"start": "cross-env NODE_ENV=development ./node_modules/.bin/webpack-dev-server --config webpack.dev.config.js",
"release": "cross-env NODE_ENV=production ./node_modules/.bin/webpack --config webpack.prod.config.js"
- 定义环境变量
// 能够提供一个设置环境变量的scripts,让你能够以unix方式设置环境变量,然后在windows上也能兼容运行
npm install --save-dev cross-env
// 可以在webpack.js里使用es6语法
npm install --save-dev babel-register
resolve 配置
resolve 配置 webpack 如何寻找模块所对应的文件。webpack 内置 JavaScript 模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你可以根据自己的需要修改默认的规则
alias | extensions |
---|---|
给你的项目常用的引用路径给别名 | 设置文件查找顺序 |
1.alias
给你的项目常用的引用路径给别名
// webpack.common.config.js添加
module.exports = {
//....
resolve: {
alias: {
'@css': '../less'
}
}
}
// second.js
// 之前
import '@css/common.less'
// 现在
import '../less/common.less'
2.extensions查找文件顺序
适配多端的项目中,可能会出现 .web.js, .wx.js,例如在转web的项目中,我们希望首先找 .web.js,如果没有,再找 .js,默认找.js
//webpack.config.js
module.exports = {
//....
resolve: {
extensions: ['web.js', '.js'] //当然,你还可以配置 .json, .css
}
}
日常实用的插件
1.copy-webpack-plugin
configs文件是如何读取的
// 安装
npm i copy-webpack-plugin -D
//webpack.common.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
//...
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, 'src/configs/configs.js'),
to: 'js/'
}
],
})
]
}
// index.html读取configs.js
<script type="text/javascript" src="./js/configs.js">
</script>
这样开发环境可生产环境都可以读取configs的配置文件
2.定义全局变量DefinePlugin
// 可以直接在webback的插件定义
new webpack.DefinePlugin({
'isDevelopment': false,
})
// second.js访问
console.log(isDevelopment)
3.清除文件夹CleanPlugin
// 安装clean-webpack-plugin
npm i copy-webpack-plugin -D
new CleanPlugin([
join(__dirname, 'dist', 'resources', 'js', '*.*'),
join(__dirname, 'dist', 'resources', 'css', '*.*'),
join(__dirname, 'dist', 'resources', 'images', '*.*'),
join(__dirname, 'dist', '*.ico'),
join(__dirname, 'dist', '*.html'),
]),
其余实用配置
1.externals
如果需要引用一个库,但是又不想让webpack打包(减少打包的时间),并且又不影响以import方式引用,那就可以通过配置externals
// index.html引用
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
// webpack.config.js
module.exports = {
...
externals: {
jquery: 'window.$'
},
...
}
// second.js引用
import $ from 'jquery';
console.log($('#root'), 222)
常用的打包优化
- webpack 的构建过程太花时间
- webpack 打包的结果体积太大
webpack-bundle-analyzer
可以直观分析打包出的文件包含哪些,大小占比如何,模块包含关系,依赖项,文件是否重复,压缩后大小如何,针对这些,我们可以进行文件分割等操作
npm install webpack-bundle-analyzer --save-dev
// webpack.config.common.js
module.exports = {
...
plugins: [
new BundleAnalyzerPlugin(),
]
...
}
- 如图
可以看出antd、react-dom、lodash、moment几个包大小最大,我们可以把他们拆出去,antd支持按需加载功能,打包出来的js也不会太大,所以不提出
第一种:代码压缩以及公用模块处理
optimization: {
// 不同的策略来分割打包出来的bundle
splitChunks: {
cacheGroups: {
// 将node_modules的库文件打包到vendors.js
vendors: {
test: chunk => (
chunk.resource &&
/\.js$/.test(chunk.resource) &&
/node_modules/.test(chunk.resource)
),
chunks: 'initial',
name: 'vendors',
},
// 将node_modules的库文件被重复引用的代码打包到async-vendors.js
'async-vendors': {
test: /[\\/]node_modules[\\/]/,
minChunks: 2,
chunks: 'async',
name: 'async-vendors'
}
}
},
// 将业务代码从入口文件分离出来,这样业务代码改变只会更改业务代码对应的js,不会影响入口文件的js,使用输出文件的chunkFilename调整名字
runtimeChunk: {
name: 'manifest'
},
minimizer: [
// js压缩
new UglifyJsPlugin({
cache: true,
parallel: true,
uglifyOptions: {
ecma: 5,
output: {
beautify: false,
},
compress: {
drop_console: true,
}
}
}),
// css压缩
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
discardComments: {
removeAll: true
},
// 避免 cssnano 重新计算 z-index
safe: true
}
}),
],
}
第二种:happypack提升打包速度
webpack 是单线程的,就算此刻存在多个任务,你也只能排队一个接一个地等待处理。这是 webpack 的缺点,好在我们的 CPU 是多核的,Happypack 会充分释放 CPU 在多核并发方面的优势,帮我们把任务分解给多个子进程去并发执行,大大提升打包效率
// 安装happypack
npm i happypack -D
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
module: {
rules: [
{
test: /\.js$/,
//把对.js 的文件处理交给id为happyBabel 的HappyPack 的实例执行
loader: 'happypack/loader?id=happyBabel',
//排除node_modules 目录下的文件
exclude: /node_modules/
},
]
},
plugins: [
new HappyPack({
//用id来标识 happypack处理那里类文件
id: 'happyBabel',
//如何处理 用法和loader 的配置一样
loaders: [{
loader: 'babel-loader?cacheDirectory=true',
}],
//共享进程池
threadPool: happyThreadPool,
//允许 HappyPack 输出日志
verbose: true,
})
]
}
第三种:dll动态链接库
当有依赖包较大时,依赖包不常改动,可以将这些依赖包通过DllPlugin打包,这样开发和生产环境构建时就可以不构建这些文件,提高打包编译速度
// 第一步添加webpack.config.dll.js,并添加配置打包对应组件
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
antd: [
'antd/lib/date-picker',
'antd/lib/table',
'antd/lib/form',
'antd/lib/steps',
'antd/lib/input',
'antd/lib/row',
'antd/lib/col',
'antd/lib/button',
'antd/lib/message',
'antd/lib/icon',
'antd/lib/cascader',
'antd/lib/checkbox',
'antd/lib/modal',
'antd/lib/upload',
'antd/lib/progress',
],
reactVendors: [
'react',
'react-dom',
'lodash',
'moment'
]
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
filename: '[name].dll.js',
path: path.resolve('dist/dll/js'),
// library必须和后面dllplugin中的name一致 后面会说明
library: '[name]_dll_[hash]'
},
plugins: [
// 接入 DllPlugin
new webpack.DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
name: '[name]_dll_[hash]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(__dirname, 'dist/dll/js', '[name].manifest.json')
}),
],
mode: 'production',
}
// 第二步,webpack.config.common.js添加对应生成的manifest.json
module.exports = {
...
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dist/dll/js/reactVendors.manifest.json')
}),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dist/dll/js/antd.manifest.json')
}),
]
...
}
// 第三步,index.ejs引用打包出的js
<script src="./dll/js/antd.dll.js" ></script>
<script src="./dll/js/reactVendors.dll.js" ></script>
前后对比
未加DllPlugin | 加了DllPlugin |
---|---|
总入口文件7.14m | 总入口文件3.56m |
输出文件3.18m | 输出文件1.47m |
Gzip压缩967k | Gzip压缩451k |
打包耗时23s | 打包耗时21s |
第五种:引用cdn
cdn优化是指把第三方库比如(vue,vue-router,axios)通过cdn的方式引入项目中,这样vendor.js会显著减少,并且大大提升项目的首页加载速度
// 第一步:引用antd、react、react-dom、moment、lodash的cdn
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.3.2/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
<script src="https://cdn.bootcss.com/moment.js/2.22.1/moment.min.js"></script>
<script src="https://cdn.bootcss.com/moment.js/2.22.1/locale/zh-cn.js"></script>
<script crossorigin="anonymous"
integrity="sha512-N8Wbuq8SeLoZ4j1Jfwva1Xd8MVNrSxVFzuxDF1YnUcNB6YncjyNtUAuPs1SSncDd98FcM/ZkvgXsr3DfwERW6A=="
src="https://lib.baomitu.com/antd/3.26.15/antd-with-locales.min.js"></script>
<link crossorigin="anonymous"
integrity="sha512-cBOfQ2h52wVLbgKbFuf6wf0qlrRhbwwAlrIMFjU3NrvRbr/OxYplBiIV4bmk1arz1g7T85HGq36hP6kAxPyt1w=="
href="https://lib.baomitu.com/antd/3.26.15/antd.min.css" rel="stylesheet">
<script crossorigin="anonymous"
integrity="sha512-131CXtlyQOY833mTVES49ngssH0LdxpKuv6NU/ex69688rmm+OSswA2yiiNAPTelbhwo0PVEts8a5RlbQnA0rA=="
src="https://lib.baomitu.com/antd/3.26.15/antd.min.js"></script>
// 第一步:webpack.config.common.js修改
module.exports = {
...
// externals告诉webpack那些资源从哪里寻找,值表示的当前环境下的变量,比如引入React之后,React被作为全局对象,webpack就回去寻找React对象
externals: {
"configs": "configs",
"react": "React",
"react-dom": "ReactDOM",
"lodash": "_",
"moment": "moment",
"antd": "antd"
}
...
}
未加cdn | 加了cdn |
---|---|
总入口文件7.14兆 | 总入口文件5.95兆 |
输出文件3.18兆 | 输出文件2.77兆 |
Gzip压缩967k | Gzip压缩810k |
打包耗时25s | 打包耗时22s |
Dll和cdn的区别
DLL | cdn缓存 |
---|---|
把公用模块打包为 DLL 文件存到项目文件夹里 | 使用网上的公用模块文件 |
第二次打包时动态链接 DLL 文件,不重新打包 | 第二次加载时直接读取缓存,不重新请求 |
打包时间缩短 | 加载时间缩短 |
第三种:gzip
开启gzip压缩可以有效压缩资源体积,压缩比率在3到10倍左右,可以大大节省服务器的网络带宽,提高资源获取的速度,节省流量,根据gzip使用的算法特性,代码相似率越大压缩效率越高
// 安装
npm i compression-webpack-plugin@2.0.0 -D
// webpack.config.common.js引入
import CompressionWebpackPlugin from 'compression-webpack-plugin';
// 添加Ï
module.exports = {
...
plugins: [
new CompressionWebpackPlugin({
filename: "[path].gz[query]", // 目标文件名
algorithm: "gzip", // 使用gzip压缩
test: new RegExp(
"\\.(js|css)$" // 压缩 js 与 css
),
threshold: 10240, // 资源文件大于10240B=10kB时会被压缩
minRatio: 0.8, // 最小压缩比达到0.8时才会被压缩
),
]
...
}
- dll更用于提升打包速度
- cdn更用于提升页面加载速度
- 压缩项目资源文件
总结
按需加载
可以看我们打包的代码会放在一起,很多时候我们不需要一次性加载所有的JS功能文件,而应该在不同阶段去加载所需要的代码。webpack内置了强大的分割代码的功能可以实现按需加载
使用webpack打包的出来的文件要实现以上的要求有两种方式,一个是webpack特有的require.ensure方法,还有一个是import方法
Webpack中的import
与正常ES6中的import语法不同,通过import函数加载的模块及其依赖会被异步地进行加载,并返回一个Promise对象。
一般正常的模块加载代码如下:
假设async.js的资源体积很大,并且我们在页面初次渲染的时候并不需要使用它,就可以对它进行异步加载。
async.js和second.js代码如下:
// async.js
console.log('我是async模块')
export const a = '模块async'
// second.js
document.querySelector('#btn').onclick = function () {
console.log('我是通过import来实现按需加载的')
let a = import('./async.js')
a.then(function (res) {
console.log('加载完成的async模块', res)
})
}
dva/router就是这种类似的原理,不会一次性加载所有页面的js,用到才加载