开发环境的基本配置
- 当我们使用less等预处理器开发样式 浏览器并不能识别、解析less文件 此时我们需要借助一个工具 将less编译成css 当我们开发js文件时 我们需要用一些ES6或更高的语法 很有可能浏览器不认识这些语法 此时我们也需要一种工具 将浏览器不能识别的语法编译成浏览器能识别的语法 可能将来你还写了一些其他东西 这些东西需要其他东西进行编译处理 上述这些小工具我们需要一个个维护 很麻烦 于是前端提出了一个概念“构建工具”
- 构建工具:我用一个大工具将小工具都包含进来 我只用关心大工具如何使用就行了 这个大工具就是构建工具 也就是说 构建工具将我们前端要做的一系列小的操作整合成一个大的工具
- webpack就是构建工具的一种
我们会在一个文件中(一般是index.js中)引入整个模块所需要的所有资源 我们首先要告诉webpack打包的起点 也就是入口文件 也就是index.js webpack会将index.js中的每个依赖都记录好 形成依赖关系树状图 然后他会通过依赖关系图的先后顺序依次把资源引进来形成chunk(代码块) 再对chunk进行各项处理 如将less编译成css 将js资源编译成浏览器能识别的语法等 这一过程称为打包 打包后 将这些处理好的资源输出出去 输出的东西叫bundle index.js jquery less等称为静态模块 经打包后生成的bundle(静态资源)就可以在浏览器上平稳运行了 所以webpack被称为静态模块打包器 - 核心概念
(1)入口(Entry)指示webpack以哪个文件为入口起点开始打包,分析构建内部依赖图。我们写的项目有很多个文件 你得告诉webpack该从哪个文件开始打包
(2)输出(Output)指示webpack打包后的资源bundles输出到哪里去,以及如何命名
(3)loader让webpack能够去处理那些非JS文件(webpack自身只理解JS)
(4)插件(Plugins)可以用于执行范围更广的任务。插件的范围包括从打包优化和压缩一直到重新定义环境中的变量等。
Loader只能翻译 一些强大得功能由Plugins提供
(5)Mode
- 运行webpack:
webpack
- loader:webpack只能理解JS和JSON文件 通过loader让webpack能够处理其他类型的文件 并将它们转换为有效模块 以供应用程序使用 以及被添加到依赖图中
- plugin:用于解决loader无法实现的其他事 可用于执行范围更广的任务 为webpack带来很大的灵活性
webpack初体验
- 打开终端
npm init
初始化包描述文件package.json package name写你自定义的包名 本文为webpack-test
其余全用默认值 - 在终端继续输入
npm i webpack webpack-cli -g
安装webpack
webpack-cli可以让我们通过指令去使用webpack中的功能 所以也要安装
-g:全局安装
- 在终端继续输入
npm install webpack webpack-cli -D
开发依赖
-D:将webpack添加到开发依赖 等价于–sever -dev
- 建议下载 安装都在项目上(最外层文件)进行 在相应的文件上运行
src
代表代码源文件夹目录 此文件夹下有一个index.js
文件作为webpack的起点文件build
代表代码经webpack打包后输出的目录- index.js代码
/*
写好index.js后 在终端输入运行指令 如下
1. 运行指令:
开发环境:webpack ./src/index.js -o ./build/built.js --mode=development
webpack会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js
整体打包环境,是开发环境
生产环境:webpack ./src/index.js -o ./build/built.js --mode=production
webpack会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js
整体打包环境,是生产环境
2. 结论:
1. webpack能处理js/json资源,不能处理css/img等其他资源
2. 生产环境和开发环境将ES6模块化编译成浏览器能识别的模块化~
3. 生产环境比开发环境多一个压缩js代码。
3. 问题
1. 不能编译打包 css、img 等文件。
2. 不能将 js 的 es6 基本语法转化为 es5 以下语法。
*/
// import './index.css';// webpack不能处理css/img等其他资源 引入这个会报错 不能进行打包
import data from './data.json';// webpack能处理js/json资源
console.log(data);
function add(x, y) {
return x + y;
}
console.log(add(1, 2));
打包样式资源:less-loader css-loader style-loader
- 使用loader来处理非js文件 在配置文件webpack.config.js中配置loader
- 注意
src
文件夹中存放的是源代码 用的ES6模块 webpack.config.js基于nodejs 使用的是commonJS - webpack.config.js代码:
/*
webpack.config.js webpack的配置文件
作用: 指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)
所有构建工具都是基于nodejs平台运行的~模块化默认采用commonjs。
*/
// resolve用来拼接绝对路径的方法 使用绝对路径 需要引入node的path模块
const { resolve } = require('path');
module.exports = {// 通过module.exports暴露
// 在这里面写webpack配置
// 入口起点
entry: './src/index.js',
// 输出
output: {
// 输出文件名
filename: 'built.js',
// 输出路径
// __dirname nodejs的变量,代表当前文件的目录绝对路径
// 在这里是C:\Users\GFQ\Documents\webpack资料\代码\2.webpack开发环境配置\03.打包样式资源
path: resolve(__dirname, 'build')
// 输出到C:\Users\GFQ\Documents\webpack资料\代码\2.webpack开发环境配置\03.打包样式资源\bulid文件夹下的built.js中
},
// loader的配置
module: {
rules: [
// 详细loader配置
// 不同文件必须配置不同loader处理
{
// 匹配哪些文件
test: /\.css$/,
// 使用哪些loader进行处理
use: [// 这里面的文件都是需要下载的npm install xx -D
// use数组中loader执行顺序:从右到左,从下到上 依次执行
// 创建style标签,将js中的样式资源插入进行,添加到head中生效
'style-loader',
// 将css文件变成commonjs模块加载到js中,里面内容是样式字符串
'css-loader'
// 当webpack遇到一个.css结尾的文件 会使用css-loader将其转换为commonjs模块(里面内容是字符串)并加载到js中
// 然后使用style-loader创建style标签 将前面那个commonjs模块插入到style标签中 将style标签添加到head标签中 让其生效
]
},
{// 配置less文件的loader
test: /\.less$/,
use: [
'style-loader',
'css-loader',
// 将less文件编译成css文件
// 需要下载 less-loader和less:npm install less-loader less -D
'less-loader'
]
}
]
},
// plugins的配置
plugins: [
// 详细plugins的配置
],
// 模式
mode: 'development', // 开发模式
// mode: 'production'
}
// 写好之后再终端使用上面的运行指令运行一下就可以了
打包HTML资源:html-webpack-plugin
- 使用插件打包HTML资源
- webpack.config.js代码:
/*
loader: 1. 下载 2. 使用(配置loader)
plugins: 1. 下载 2. 引入 3. 使用
*/
const { resolve } = require('path');
// 下载:npm i html-webpack-plugin -D
const HtmlWebpackPlugin = require('c');// 引入
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
]
},
plugins: [
// plugins的配置
// html-webpack-plugin
// 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)他会自动引 不要再自己手动引了
// 他默认创建的是空的 我们需要有结构的HTML文件:配置template属性
new HtmlWebpackPlugin({
// 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)built.js
template: './src/index.html'// 要引入的HTML文件
// 我给了插件一个路径./src/index.html 他会自己去找这个文件 然后把这个文件复制进来
// 然后输出去 在输出前 会将打包输出的所有资源built.js引入进来
// 如果是js文件 就是用script标签引进来 如果是css文件 就通过link标签引进来
})
],
mode: 'development'
};
打包后的index.html文件如下所示 会多一个script标签
打包图片资源:url-loader html-loader
webpack.config.js代码:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.less$/,
// 要使用多个loader处理用use
use: ['style-loader', 'css-loader', 'less-loader']
},
{
// 问题:默认处理不了html中img图片 在HTML中用img标签引入的图片 默认处理不了 因为他根本无法解析html
// 处理图片资源
test: /\.(jpg|png|gif)$/,
// 下载 url-loader file-loader
loader: 'url-loader',
options: {// 使用options可以配置这个loader
// url-loader在打包图片时 并不是原封不动的输出
// 他发现图片大小小于8kb(根据limit属性设置的),图片就会被base64处理
// 优点: 减少请求数量(减轻服务器压力)
// 缺点:图片体积会更大(文件请求速度更慢)所以我们一般只对8-12kb左右的图片进行base64处理
limit: 8 * 1024,// 8kb
// 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
// 解析时会出问题:img标签的src=[object Module]
// 解决:关闭url-loader的es6模块化,使用commonjs解析
esModule: false,
// 使用webpack打包后的图片名称是根据图片内容生成的哈希值 很长 所以我们可以给图片进行重命名
// [hash:10]取图片的hash的前10位
// [ext]取文件原来扩展名
name: '[hash:10].[ext]'
}
},
{
test: /\.html$/,
// 处理html文件的img图片(html-loader负责引入img(使用commonjs引入),从而能被url-loader进行处理)
loader: 'html-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development'
};
// 我们发现 一张图片在页面中重复使用 但使用webpack打包后只生成了一张图片
// 所以webpack在解析时发现我们使用了同一个文件 他不会重复打包这个文件 而只输出一次
打包其他资源:file-loader
- 其他资源:我们不需要做任何处理 只要原封不动的输出出去就行
- webpack.config.js代码:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
// 打包其他资源(除了html/js/css资源以外的资源)
{
// 排除css/js/html/less资源 将前面处理过的资源都排除掉 其余资源都在这里处理
exclude: /\.(css|js|html|less)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development'
};
devServe
- 我们在写代码的时候 写好一部分代码后想看看效果 需要将代码打包然后运行 然后继续写代码 又要重新打包然后运行 每次改了代码都要重新打包 重新运行 很麻烦 我们可以使用devServer来自动打包运行
- webpack.config.js代码:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
// 打包其他资源(除了html/js/css资源以外的资源)
{
// 排除css/js/html/less资源
exclude: /\.(css|js|html|less)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development',
// 开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~~)
// 特点:只会在内存中编译打包,不会有任何输出
// 启动devServer指令为:npx webpack-dev-server
devServer: {
// 项目构建后路径
contentBase: resolve(__dirname, 'build'),
// 启动gzip压缩
compress: true,
// 端口号
port: 3000,
// 自动打开浏览器
open: true
}
};
开发环境基本配置
- 对前面的配置做一个汇总 webpack.config.js代码:
/*
开发环境配置:能让代码运行
运行项目指令:
webpack 会将打包结果输出出去
npx webpack-dev-server 只会在内存中编译打包,没有输出
*/
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
{
// 处理less资源
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
// 处理css资源
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
// 处理图片资源
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
// 关闭es6模块化
esModule: false,
outputPath: 'imgs'
}
},
{
// 处理html中img资源
test: /\.html$/,
loader: 'html-loader'
},
{
// 处理其他资源
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins: [
// plugins的配置
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true
}
};
给代码划分了一下目录 如下所示
我们希望 使用webpack打包后的文件也像源文件一样分级 可以在每个loader下配置自己的outputPath属性
生产环境的基本配置
生产环境做的事:
(1)css文件经过之前的处理整合到了js文件中 这会使得js文件体积特别大 下载变慢 因为是先加载Js文件 然后创建style标签 将css文件插入进去 会出现闪屏 所以我们需要将将css从js文件中提取出来
(2)压缩代码
(3)处理css代码和js代码的兼容问题
等等
提取 css 成单独文件:mini-css-extract-plugin
- 在终端输入
npm i mini-css-extract-plugin -D
安装插件并在 webpack.config.js中引入const MiniCssExtractPlugin = require('mini-css-extract-plugin');
- 我们知道 经
webpack.config.js
处理后CSS文件就在JS文件中了style-loader
会创建一个style标签 将处理后的CSS文件插入到页面上 但是我们现在要让CSS文件分离出来 成为一个单独的文件 所以我们要用MiniCssExtractPlugin.loader
取代style-loader
- webpack.config.js中的代码
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');// 引入
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
// 创建style标签,将样式放入
// 'style-loader',
// 这个loader取代style-loader。作用:提取js中的css成单独文件
MiniCssExtractPlugin.loader,
// 将css文件整合到js文件中
'css-loader'
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
// 对输出的css文件进行重命名
filename: 'css/built.css'// 如果不配置这个属性 那么css文件会被提出到build文件夹的main.css中
// 我们对其重命名是希望提取后的css文件依旧存在层级关系
})
],
mode: 'development'
};
运行后发现build文件夹下会多一个css文件夹 里面就有打包的built.css文件 其实我们源代码中有好几个css文件 但最终都打包到了built.css中 在打包后的index.html文件中 link标签将打包好的CSS文件引入进来了
CSS兼容性处理:postcss-loader postcss-preset-env
- 使用postcss实现CSS兼容性处理 要想在webpack中使用postcss需要postcss-loader和postcss-preset-env 所以在终端:
npm i postcss-loader postcss-preset-env -D
下载这两个库 - 使用:
(1)使用loader的默认配置 在use中输入:'postcss-loader'
(2)在options中修改loader的配置 引入postcss-preset-env插件 - postcss-preset-env:帮postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式
- 因为我们下载库都是在外面下载的 所以package.json也在外面 文件层级关系如下:
- 配置package.json中的browserslist package.json中的代码如下
更多配置详见GitHub上browserslist的仓库 - 默认情况下 是根据生产环境production的配置进行兼容 不管你配置的mode是啥 如果想根据开发环境的配置进行兼容 需要添加一行代码
process.env.NODE_ENV = 'development';
- webpack.config.js中的代码
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 设置nodejs环境变量
// process.env.NODE_ENV = 'development';
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
/*
css兼容性处理:postcss --> postcss-loader postcss-preset-env
postcss-preset-env:帮postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式
"browserslist": {
// 开发环境 --> 要想以开发环境来开发 设置node环境变量:process.env.NODE_ENV = development
"development": [
"last 1 chrome version",// 兼容最新的浏览器的版本
"last 1 firefox version",
"last 1 safari version"
],
// 生产环境:默认是看生产环境 跟我们下面配置的mode: 'development'没关系
"production": [
">0.2%",// 大于99.8%的浏览器
"not dead",// 不要已经死掉的浏览器 比如IE10
"not op_mini all"// op_mini浏览器早就死亡了 废弃了 所有的都不要
]
}
*/
// 使用loader的默认配置
// 'postcss-loader',
// 修改loader的配置
{
loader: 'postcss-loader',
options: {
ident: 'postcss',// 固定写法
plugins: () => [
// 使用postcss的一些插件 插件的返回值得是个数组
require('postcss-preset-env')()
]
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/built.css'
})
],
mode: 'development'
};
压缩CSS:css-loader optimize-css-assets-webpack-plugin
- 在终端输入
npm i optimize-css-assets-webpack-plugin -D
下载并引入 - webpack.config.js中的代码
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')// 引入 新增
// 设置nodejs环境变量
// process.env.NODE_ENV = 'development';
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
// postcss的插件
require('postcss-preset-env')()
]
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
// 压缩css
new OptimizeCssAssetsWebpackPlugin()// 新增
],
mode: 'development'
};
JS语法检查eslint:eslint-config-airbnb-base eslint-plugin-import eslint
- 语法检查:我们在团队工作时 我们希望团队里面每个人写的代码风格相似 这时我们就可以用js语法检查 它可以让你的代码写的更规范 同时他还能检查常见的语法错误
- 使用eslint进行语法检查 在webpack中eslint依赖eslint-loader 所以我们需要下载这两个包
- 通过在package.json中配置eslintConfig来设置检查规则 推荐使用airbnb规则 airbnb:JS代码风格指南 在GitHub上可以查看详情
- 在npm官网查询eslint 发现:
(1)eslint-config-airbnb-base
(2)eslint-config-airbnb
他们都是将airbnb应用于eslint的库 二者的区别是eslint-config-airbnb-base不包含react的插件 其中eslint-config-airbnb-base包含eslint-config-airbnb-legacy(包含ES5及以下)和eslint-config-airbnb-base(包含ES6及以上 ) 综合来看 本文使用eslint-config-airbnb-base - 要想使用eslint-config-airbnb-base 需要下载两个库:eslint-plugin-import eslint 所以我们一共需要下载三个库:eslint-config-airbnb-base eslint-plugin-import eslint 在终端输入
npm i eslint-config-airbnb-base eslint-plugin-import eslint -D
安装 - package.json中配置eslintConfig:
"eslintConfig": {
"extends": "airbnb-base"// // 继承airbnb-base提供的eslint语法检查
"env": {
"browser": true// 支持浏览器全局变量
// eslint并不认识浏览器的语法 但浏览器一定会有window 所以还需要配置这个属性
}
}
- webpack.config.js中的代码
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.js$/,
// 注意:只检查自己写的源代码,第三方的库是不用检查的
exclude: /node_modules/,// 排除第三方的库 第三方的库是不用检查 他们已经检查过了 只检查我们自己写的源代码就行
loader: 'eslint-loader',
options: {
// 自动修复eslint的错误 即自动修复我们的js语法错误
fix: true
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development'
};
- 可以在js文件中写
eslint-disable-next-line
让下一行eslint所有规则都失效 也就是说下一行不进行eslint检查 在调试时可以加这一行 代码要发布上线千万不能写这一句 - eslint检测流程也是需要将源码转换为 AST,然后再利用 AST 来检查代码规范化的问题。
JS兼容性处理babel
- 使用babel进行兼容性处理 在webpack中想使用babel需要安装babel-loader和@babel/core 此外我们需要@babel/preset-env对预设环境进行兼容性处理 在终端输入
npm i babel-loader @babel/core @babel/preset-env -D
下载这三个包 rules中的代码如下
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 预设:指示babel做怎么样的兼容性处理
presets: ['@babel/preset-env']
}
}
- 现在我们只能对基本的JS语法进行兼容 我们可以使用@babel/polyfill对所有的JS语法进行兼容性处理 在终端输入
npm i@babel/polyfill -D
下载 在JS文件中引入这个包import '@babel/polyfill';
就可以使用了 webpack.config.js中的内容同上 - 但是@babel/polyfill会将与JS兼容性有关的东西全部引进来 但我只需要解决部分兼容性问题 这样做让我们的代码体积变大了很多 而且没有必要 我们可以使用core-js进行按需加载 我们哪里需要进行兼容性处理 你就加载需要的库就行 在终端输入
npm i core-js -D
下载 然后在webpack.config.js中进行配置
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
/*
js兼容性处理:babel-loader @babel/core
1. 基本js兼容性处理 --> @babel/preset-env
问题:只能转换基本语法,如promise高级语法不能转换
2. 全部js兼容性处理 --> @babel/polyfill
问题:我只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了~
3. 需要做兼容性处理的就做:按需加载 --> core-js
*/
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 预设:指示babel做怎么样的兼容性处理
presets: [
[
'@babel/preset-env',// 预设环境的兼容性处理 固定写法 一定要写 就算你后来用了@babel/polyfill core-js 这个也一定要写
{// 不适用按需加载的话 这个对象里的代码都不用写
// 按需加载
useBuiltIns: 'usage',
// 指定core-js版本
corejs: {
version: 3
},
// 指定兼容性做到哪个版本浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development'
};
- @babel/polyfill和core-js不兼容 只能同时使用一种 但是不管你使用哪一种 不管使不使用这两种 都要写@babel/preset-env
- Babel 的工作原理就是先将 ES6 源码转换为 AST,然后再将 ES6 语法的 AST 转换为 ES5 语法的 AST,最后利用 ES5 的 AST 生成 JavaScript 源代码。
压缩HTML、JS
- 只要将mode改为production启用生产环境就可以进行JS压缩了 因为在生产环境下webpack会自动加载很多配置 其中UglifyJsPlugin插件就会压缩JS代码 webpack.config.js中代码
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
// 生产环境下会自动压缩js代码
mode: 'production'
};
- HTML不用做兼容性处理 只需要压缩 配置HtmlWebpackPlugin插件的minify属性即可 webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
// 压缩html代码
minify: {
// 移除空格
collapseWhitespace: true,
// 移除注释
removeComments: true
}
})
],
mode: 'production'
};
生产环境基本配置
将上面的内容整合在一起 webpack.config.js中代码如下
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';
// 复用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
// 还需要在package.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
}
];
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
// 使用less-loader将less文件编译成css文件
// 然后使用postcss-loader对css进行兼容性处理
// 最后使用MiniCssExtractPlugin.loader将css文件提取成单独的文件
},
/*
正常来讲,一个文件只能被一个loader处理。
当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
先执行eslint 在执行babel
因为eslint用于语法检查 如果先做其他处理 后面一旦我们检查出语法错误 又要更改语法 前面做的都要推翻重来
babel用于兼容性处理 会将ES6语法转为ES5语法 转换后用eslint进行语法检查会报错
就算你设置了自动更改错误语法 那么你刚转换的ES5语法白转换了 他这里又会给你转换为ES6语法
*/
{
// 在package.json中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_modules/,
// 优先执行 不管这个loader放哪里 他永远优先执行
enforce: 'pre',// 正常情况下 loader的执行顺序没有限制 但是我们希望eslint先执行 所以为eslint设置这个属性
loader: 'eslint-loader',
options: {
fix: true
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
]
}
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production'
};
性能优化
开发环境性能优化
优化打包构建速度——HMR
- HMR: hot module replacement 热模块替换 / 模块热替换
作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块)极大提升构建速度 - 样式文件:可以使用HMR功能:因为style-loader内部实现了
这就是为什么我们在开发环境用style-loader 在生产环境得提取为单独的文件 因为开发环境使用style-loader能让性能更好 打包更快 但是在生产环境里代码需要上线 我们需要考虑代码的性能优化
- js文件:默认不能使用HMR功能 如果你想使用HMR 需要修改js代码,添加支持HMR功能的代码 在入口文件index.js中:
if (module.hot) {
// 一旦 module.hot 为true,说明开启了HMR功能。接下来我们就让HMR功能代码生效
module.hot.accept('./print.js', function() {
// 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
// 会执行后面的回调函数 打包构建print.js
print();
});
}
// 如果你还想监听其他的js文件 可以在后面继续写if (module.hot) {...}
- 注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。因为入口文件会将其他所有的文件全都引入 一旦入口文件变化 其他文件又会重新引入重新加载 我们是无法阻止的
- html文件: 默认不能使用HMR功能 同时会导致问题:html文件不能热更新了(不用做HMR功能)
热更新:本地写的代码并没有重新编译 并没有重新刷新浏览器 就运行了
- 解决html文件不能热更新:修改entry入口,将html文件引入
HTML为什么没有HMR 因为HMR是当一个模块发生变化 只会打包这一个模块 而不是打包所有模块 而项目中只有一个HTML模块 只要HTML发生变化 一定会打包这一个文件 一定要全部打包的 没法优化
- webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// entry: ./src/js/index.js,
entry: ['./src/js/index.js', './src/index.html'],// 把上一行代码改成这一行 把html文件加进来 就可以进行热更新了 但依旧不能使用HMR功能
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
{
// 处理less资源
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
// 处理css资源
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
// 处理图片资源
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
// 关闭es6模块化
esModule: false,
outputPath: 'imgs'
}
},
{
// 处理html中img资源
test: /\.html$/,
loader: 'html-loader'
},
{
// 处理其他资源
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins: [
// plugins的配置
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
// 开启HMR功能
// 当修改了webpack配置,新配置要想生效,必须重新webpack服务
hot: true
}
};
优化代码调试——source-map
- webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: ['./src/js/index.js', './src/index.html'],
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
{
// 处理less资源
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
// 处理css资源
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
// 处理图片资源
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
// 关闭es6模块化
esModule: false,
outputPath: 'imgs'
}
},
{
// 处理html中img资源
test: /\.html$/,
loader: 'html-loader'
},
{
// 处理其他资源
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins: [
// plugins的配置
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
hot: true
},
devtool: 'eval-source-map'// 会生成一个.map文件 新增
};
- source-map: 一种提供源代码到构建后代码映射的技术 (如果构建后代码出错了,通过映射可以追踪到源代码的错误)
- 配置webpack.config.js中的devtool属性即可开启source-map技术 devtool属性的取值:
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
(1)source-map:外部
可以提供:错误代码准确信息 和 源代码的错误位置
(2)inline-source-map:内联 在打包后的built.js中生成一个base64编码的source-map文件 它只生成一个内联source-map
可以提供:错误代码准确信息 和 源代码的错误位置
(3)hidden-source-map:外部
可以提供:错误代码错误原因,但是没有错误位置 不能追踪源代码错误,只能提示到构建后代码的错误位置
(4)eval-source-map:内联 每一个文件都生成对应的source-map,都在eval函数中 并内嵌在打包后的built.js中
可以提供:错误代码准确信息 和 源代码的错误位置
react vue脚手架里用的这个
(5)nosources-source-map:外部
可以提供:错误代码准确信息, 但是没有任何源代码信息
(6)cheap-source-map:外部
可以提供:错误代码准确信息 和 源代码的错误位置 只能精确的行 别的都能精确到行和这一行的哪一个地方出错了 但cheap-source-map只能精确到哪一行
(7)cheap-module-source-map:外部 module会将loader的source map加入
可以提供:错误代码准确信息 和 源代码的错误位置 只能精确的行 别的都能精确到行和这一行的哪一个地方出错了 但cheap-source-map只能精确到哪一行
内联 和 外部的区别:1. 外部生成了文件,内联没有 2. 内联构建速度更快
- 开发环境:考虑:速度快,调试更友好
(1)速度快(eval>inline>cheap>…)(从下到上越来越快)
eval-cheap-souce-map
eval-source-map
(2)调试更友好(从下到上越来越友好)
souce-map
cheap-module-souce-map
cheap-souce-map
综合来看eval-source-map / eval-cheap-module-souce-map 这两种最好(兼顾速度快 调试友好)不过左边调试更友好 右边速度更快
- 生产环境:考虑:源代码要不要隐藏? 调试要不要更友好
注意:内联会让代码体积变大,所以在生产环境不用内联
(1)源代码要隐藏:
nosources-source-map 全部隐藏
hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
(2)调试从好到差(从左到右):①source-map②cheap-source-map/cheap-module-source-map③nosources-source-map/hidden-source-map
不隐藏源代码可以使用下面两种方法 只考虑调试用左边 要想速度快用右边:source-map / cheap-module-souce-map
生产环境性能优化
- 优化打包构建速度
- oneOf
- babel缓存
- 多进程打包
- externals
- dll
- 优化代码运行的性能
- 缓存(hash-chunkhash-contenthash)
- tree shaking
- code split
- 懒加载/预加载
- pwa
oneOf
以前rules中的代码:
rules: [
// loader的配置
{
// 处理less资源
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
// 处理css资源
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
// 处理图片资源
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
// 关闭es6模块化
esModule: false,
outputPath: 'imgs'
}
},
{
// 处理html中img资源
test: /\.html$/,
loader: 'html-loader'
},
{
// 处理其他资源
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
但是 对于一种文件 他只可能匹配一项 比如less文件只会被第一个loader处理 但是如果你这样写的话 它处理完了还会去匹配其他的loader 没必要 所以我们可以使用oneof进行优化 oneof中的loader只会匹配并执行一个 所以oneof中不能有两个loader来处理同一个文件 像上面的babel-loader和eslint-loader都是处理js文件的 要想两个都处理JS文件 则需要从oneof中提取一个出来 不能把两个都放在oneof中 使用rules优化后的代码:
rules: [
{
// 在package.json中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_modules/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
oneOf: [
// 以下loader只会匹配一个
// 注意:不能有两个配置处理同一种类型文件
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
]
}
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
}
]
缓存
- babel缓存:在写代码时 永远是JS代码最多 babel会对我们写的JS代码进行编译处理 编译成我们浏览器能识别的语法 即兼容性处理 假如我有100个JS模块 我只改动了其中1个JS模块 我希望只编译这一个JS模块 其他99个是不变的 这和HMR功能很像 但HMR是基于dev-serve的 生产环境不需要dev-serve 生产环境中为了实现这一需求可以开启babel缓存 在babel-loader的options中添加代码:
cacheDirectory: true
可以将babel之间编译的100个文件缓存 以后如果这100个文件没有改变 会直接使用缓存 而不会再重新打包一次 - 文件资源缓存:当我们的代码上线后 都会对资源做缓存处理(强制缓存 协商缓存) 这样用户第二次访问时会快很多 假如资源在强制缓存期间出现了bug 我们需要更改代码 但由于资源被强制缓存了 不会向服务器发请求 每次都是直接从缓存中拿 也就是说 在强制缓存期间 我不想走缓存 我该怎么办?此时我们可以更改资源名称 比如给资源名称增加版本号等 那么下次我更新时 更新了版本号 资源名称变了 就会重新发请求 所以我们可以给文件名加上hash值
(1)hash: 每次wepack构建时 会生成一个唯一的hash值。不管你文件有没有变化 每次webpack构建时 这个hash值都会变化
问题: 因为js和css使用的都是webpack打包生成的hash值 如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)CSS和JS分开缓存了 但是他们公用同一个hash值 假如js文件改了CSS没变化 这个hash值也会改变 导致JS缓存和CSS缓存都失效了
(2)chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
问题: js和css的hash值还是一样的 因为css是在js中被引入的,所以同属于一个chunk
(3)==contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样 == - webpack.config.js中代码如下
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';
// 复用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
// 还需要在package.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
}
];
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.[contenthash:10].js',// 文件名加哈希值 这样可以不走缓存
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
// 在package.json中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_modules/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
// 以下loader只会匹配一个
// 注意:不能有两个配置处理同一种类型文件
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
/*
正常来讲,一个文件只能被一个loader处理。
当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
先执行eslint 在执行babel
*/
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true
}
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.[contenthash:10].css'// 文件名加哈希值 这样可以不走缓存
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production',
devtool: 'source-map'
};
tree shaking
- tree shaking:去除无用代码 减少代码体积
- 任何开启tree shaking:1. 必须使用ES6模块化 2. 开启production环境
- 以上方法会有个Bug 可能会将css当作无用代码 给去除了 模拟测试:在package.json中配置:
"sideEffects": false
表明所有代码都可以进行tree shaking 此时会发现打包后的built文件中没有css文件 - 解决:在package.json中配置:
"sideEffects": ["*.css", "*.less"]
你可以在数组里面标记不进行tree shaking的资源
code split
- 代码分割:将我们打包输出的一个文件分割成多个文件 这样加载时我们可以并行加载这么多文件 加载速度更快 此外我们可以据此实现按需加载 我需要用这个文件我再加载 不需要用就不加载
- 方法一:设置多个入口文件 一个入口文件会输出一个bundle 多个入口文件就会打包输出多个bundle webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 单入口 单页面应用的配置
// entry: './src/js/index.js',
entry: {
// 多入口:有一个入口,最终输出就有一个bundle 多页面应用的配置方法
index: './src/js/index.js',// index就是文件名 就是下面[name]的值
test: './src/js/test.js'// test就是文件名 就是下面[name]的值
},
output: {
// [name]:取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production'
};
问题:假如我今天有一个入口 明天有两个入口 后天有三个入口 每次都需要改 改来改去很麻烦
- 方法二:配置optimization属性 webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 单入口
// entry: './src/js/index.js',
entry: {
// 多入口:有一个入口,最终输出就有一个bundle 多页面应用的配置方法
index: './src/js/index.js',// index就是文件名 就是下面[name]的值
test: './src/js/test.js'// test就是文件名 就是下面[name]的值
},
output: {
// [name]:取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
/*
功能:
1. 可以将node_modules中代码单独打包一个chunk最终输出
这样可以把别人第三方的东西打包在一起 把我们自己写的代码打包在一起 两个分开打包
2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
*/
optimization: {
splitChunks: {
chunks: 'all'
}
},
mode: 'production'
};
问题:针对单页面应用 他只会把node_modules中代码单独打包成一个chunk并输出 将我们写的所有代码都打包成一个chunk 但是我们希望 我们写的代码可以打包成多个chunk
- 方法三:在js代码中 使用import动态导入语法,让某个文件被单独打包成一个chunk index.js中代码如下
function sum(...args) {
return args.reduce((p, c) => p + c, 0);
}
/*
import动态导入语法:能将某个文件单独打包 这里将./test文件单独打包
*/
/* webpackChunkName: 'test' */ // 这个注释会对你打包后的chunk进行重命名 重命名为test
import(/* webpackChunkName: 'test' */'./test')
.then(({ mul, count }) => {
// 文件加载成功~
// eslint-disable-next-line
console.log(mul(2, 5));
})
.catch(() => {
// eslint-disable-next-line
console.log('文件加载失败~');
});
// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));
JS懒加载和预加载
- 懒加载:当文件需要使用时才加载 使用import动态导入语法 懒加载是一定会进行代码分割的
index.js文件中的代码如下
console.log('index.js文件被加载了~');
document.getElementById('btn').onclick = function() {
import(/* webpackChunkName: 'test' */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
};
这样 点击按钮才会加载./test文件
- 预加载:会在使用之前,提前加载js文件 存在兼容性问题 配置注释使用
预加载和正常加载的区别:正常加载可以认为是并行加载(同一时间加载多个文件) 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
index.js文件中的代码如下
console.log('index.js文件被加载了~');
// import { mul } from './test';// 正常加载
document.getElementById('btn').onclick = function() {
import(/* webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
};
PWA:workbox-webpack-plugin
- PWA:渐进式网络开发应用程序(离线可访问) 在离线状态下也可以访问网站
- 通过workbox来使用PWA技术 在webpack中 需要借助workbox-webpack-plugin插件来使用PWA技术 在终端输入
npm i workbox-webpack-plugin -D
下载该插件并引入 - webpack.config.js中代码如下
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';
// 复用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
// 还需要在package.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
}
];
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.[contenthash:10].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
// 在package.json中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_modules/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
// 以下loader只会匹配一个
// 注意:不能有两个配置处理同一种类型文件
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
/*
正常来讲,一个文件只能被一个loader处理。
当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
先执行eslint 在执行babel
*/
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true
}
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.[contenthash:10].css'
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
new WorkboxWebpackPlugin.GenerateSW({// 通过WorkboxWebpackPlugin实例的GenerateSW方法才能使用PWA技术
/*
1. 帮助serviceworker快速启动
2. 删除旧的 serviceworker
最终会生成一个 serviceworker 配置文件~ 接下来通过serviceworker配置文件来注册serviceworker 在入口文件中配置
*/
clientsClaim: true,
skipWaiting: true
})
],
mode: 'production',
devtool: 'source-map'
};
- index.js中代码如下
import { mul } from './test';
import '../css/index.css';
function sum(...args) {
return args.reduce((p, c) => p + c, 0);
}
// eslint-disable-next-line
console.log(mul(2, 3));
// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));
/*
1. eslint不认识 window、navigator全局变量
解决:需要修改package.json中eslintConfig配置
"env": {
"browser": true // 支持浏览器端全局变量
}
2. serviceWorker代码必须运行在服务器上
--> nodejs
-->
npm i serve -g 帮助我们快速创建一个静态资源服务器
serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
*/
// 注册serviceWorker
// 处理兼容性问题
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
// 绑定load事件 等全局资源加载完毕再注册serviceWorker
navigator.serviceWorker
.register('/service-worker.js')
// /service-worker.js这个文件会由webpack.config.js中的new WorkboxWebpackPlugin.GenerateSW生成
.then(() => {
console.log('sw注册成功了~');
// 注册成功后 以后断网了 浏览器就会从serviceWorker中加载资源 就可以拿到我之前通过serviceWorker缓存的资源
})
.catch(() => {
console.log('sw注册失败了~');
});
});
}
多进程打包
- JS主线程是单线程的 同一时间只能干一件事 如果我的事情比较多 他就要排队等很久很久才能干下一件事 会比较慢 所以我们可以通过多进程来优化打包速度 同一时间 我有好几个进程来干这个事 会快一些 终端输入:
npm i thread-loader -D
下载 - 一般给babel-loader使用thread-loader babel-loader工作时 就会开启多进程了
- webpack.config.js中代码如下
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';
// 复用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
// 还需要在package.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
}
];
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.[contenthash:10].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
// 在package.json中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_modules/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
// 以下loader只会匹配一个
// 注意:不能有两个配置处理同一种类型文件
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
/*
正常来讲,一个文件只能被一个loader处理。
当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
先执行eslint 在执行babel
*/
{
test: /\.js$/,
exclude: /node_modules/,
use: [
/*
开启多进程打包。
进程启动大概为600ms,进程通信也有开销。
只有工作消耗时间比较长,才需要多进程打包
假如你这个工作100ms就干完了 你交给多进程去做 光进程启动就要花600Ms 得不偿失
由于项目中JS文件比较多 所以我们首先想到打包JS时进行优化
会对JS进行处理的loader有两个:eslint-loader babel-loader
前者只进行语法检查 后者需要进行编译、转换等 消耗时间比较长 也是我们工作时间最长的loader
所以我们优先想着对babel-loader进行优化
*/
// 'thread-loader',// 它每次启动会根据CPU核数-1的数量来启动进程 如果你想指定 可以写下面这个对象里的代码
{
loader: 'thread-loader',
options: {
workers: 2 // 调整进程数为2个
}
},
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true
}
}
]
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.[contenthash:10].css'
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true
})
],
mode: 'production',
devtool: 'source-map'
};
externals
- externals:防止将某些包打包到我们最终输出的bundle中
- webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'production',
externals: {
// 拒绝jQuery被打包进来
// 这里面的包不准打包进bundle中
// 库名: 'npm下载的包名'
jquery: 'jQuery'
}
};
- 可以在index.js文件中 通过script标签引入jQuery使用 不引入的话就相当于你没有用
- 应用场景:希望某些包不打包进最终输出的bundle中 少打包一个包可以提升打包速度 不打包的包我们可以通过script标签引入使用
dll
- dll:动态链接库 类似于externals 会告诉webpack那些库不参与打包 不同的是 dll会对某些库进行单独打包将多个库打包成一个chunk
- 由于我们对代码做了分割 node_modules中的代码会被我们打包成一个chunk 但是我们的第三方库又特别特别多 如果全部打包成一个文件 那么这个文件体积太大了 所以通过dll技术 可以将这些第三方库单独拆分出来 打包成不同的chunk 有利于性能优化
- 我们需要重新创建个配置文件 再这里对某些库进行单独打包 文件名随便取 这里取webpack.dll.js 内容如下:
/*
使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包
当你运行 webpack 时,默认查找 webpack.config.js 配置文件
现在我们需要运行 webpack.dll.js 文件 运行指令得改为:
--> webpack --config webpack.dll.js
*/
const { resolve } = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 最终打包生成的[name] --> jquery
// ['jquery'] --> 要打包的库是jquery 以后你有别的和jquery相关的库也可以放在这个数组里['jquery', 'xxx']
jquery: ['jquery'],
},
output: {
filename: '[name].js',// [name]与上面那个“最终打包生成的[name]”一致
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字
},
// 接下来我们需要简历一个依赖关系 告诉webpack将来打包时不要再打包jquery了 此时需要借助webpack插件来生成一个映射文件
plugins: [
// 打包生成一个 manifest.json --> 提供和jquery映射 这样webpack就知道jquery库不需要打包 并且jquery库和[name]_[hash]相对应
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射库的暴露的内容名称
path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
})
],
mode: 'production'
};
在终端输入:webpack --config webpack.dll.js
运行webpack.dll.js文件 会对jquery库进行单独打包 并生成manifest.json文件提供映射关系
- 以后jquery库就不用打包了 直接在源代码中引入就可以了 我们还需要更改webpack.config.js中的配置:
(1)使用webpack插件告诉webpack哪些库不参与打包,同时使用时的名称也得变
(2)我们发现输出的打包文件built.js文件中就没有jquery库 所以还需要使用add-asset-html-webpack-plugin插件将jquery库打包输出出去 输出出来之后add-asset-html-webpack-plugin插件会自动在built.js文件中引入它 - webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
// 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// webpack不打包jquery库 在输出的打包文件built.js文件中就没有jquery库
// 所以我们还需要将jquery库打包输出出去 再在built.js文件中引入它
// 将某个文件打包输出去,并在html中自动引入该资源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
mode: 'production'
};
之后你的源代码改了 jquery没改 只用运行webpack.config.js就可以了
webpack配置详解
entry
entry: 入口起点
- string -->
'./src/index.js'
(1)单入口
(2)打包形成一个chunk(不管你源代码有几个文件 都打包在一个chunk中)。 输出一个bundle文件。
(3)此时chunk的名称默认是 main - array -->
['./src/index.js', './src/add.js']
(1)多入口
(2)所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
(3)此时chunk的名称默认是 main
(4)作用:只有在HMR功能中让html热更新生效~ - object
(1)多入口
(2)有几个入口文件就形成几个chunk,输出几个bundle文件
(3)此时chunk的名称是 key
entry: {
index: './src/index.js', // 这个文件打包到index.js文件中
add: './src/add.js'// 这一个文件打包到add.js文件中
}
- webpack.config.js中代码如下
module.exports = {
// entry: './src/index.js',
entry: {
index: ['./src/index.js', './src/count.js'], // 这两个文件打包到index.js文件中
add: './src/add.js'// 这一个文件打包到add.js文件中
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'build')
},
plugins: [new HtmlWebpackPlugin()],
mode: 'development'
};
output
webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
// 文件名称 可以指定名称、目录
filename: 'js/[name].js',
// 输出文件目录(将来所有资源输出的公共目录)
path: resolve(__dirname, 'build'),
// 所有资源引入公共路径前缀 --> 'imgs/a.jpg' --> '/imgs/a.jpg'
// 假如一张图片的路径是imgs/a.jpg 经过publicPath: '/'处理后就变成了/imgs/a.jpg
// imgs/a.jpg代表在当前路径下去找imgs/a.jpg
// /imgs/a.jpg会以当前服务器地址做补充 取服务器根目录下找imgs目录 再在里面找a.jpg 代码上线后更倾向于使用这种
publicPath: '/',// 路径前加不加/ 比如我script标签引入了某个资源 这个资源前面加不加/ 样式文件通过link标签引入 路径前加不加/ 等
chunkFilename: 'js/[name]_chunk.js', // 非入口chunk的名称 entry指定的文件就叫入口文件
// 非入口chunk:(1)import语法动态导入某个文件时 会将一个文件单独分割成一个chunk 这个chunk就会采用chunkFilename命名
// (2)通过optimization将node_modules中的东西分割成单独chunk 这些chunk就会采用chunkFilename命名
// library: '[name]', // 打包后的main.js整个库向外暴露的变量名 打包后的main.js中的内容向外暴露的变量名 外面可以直接引用使用
// libraryTarget: 'window' // 变量名添加到哪个上 browser
// libraryTarget: 'global' // 变量名添加到哪个上 node
// libraryTarget: 'commonjs'
},
plugins: [new HtmlWebpackPlugin()],
mode: 'development'
};
module
- module中我们一般只写rules rules中写loader的配置
- webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'js/[name].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
{
// test中写正则匹配规则 你要匹配的文件名
test: /\.css$/,
// 多个loader用use
use: ['style-loader', 'css-loader']
},
{
test: /\.js$/,
// 排除node_modules下的js文件
exclude: /node_modules/,
// 只检查 src 下的js文件
include: resolve(__dirname, 'src'),
// 优先执行
enforce: 'pre',
// 延后执行
// enforce: 'post',
// 单个loader用loader
loader: 'eslint-loader',
options: {}
},
{
// 以下配置只会生效一个
oneOf: []
}
]
},
plugins: [new HtmlWebpackPlugin()],
mode: 'development'
};
resolve
resolve
:解析模块的规则- 将来我们写项目的时候 文件可能会嵌套很多层 目录层级会很深 要回过头去找某个目录会很费劲 配置路径别名就可以解决这个问题 原本你要写
../css/index.css
配置了resolve的alias属性
alias: {
$css: resolve(__dirname, 'src/css')
}
后 你就可以写$css/index.css
了 他自己会通过$css找到绝对路径 再去里面找index.css
extensions
:配置省略文件路径的后缀名 默认值是extensions: ['.js', '.json']
假如你有个index.css文件 配置了下面这段代码后 你就可以写index了 他会先匹配index.js 发现没有这个文件 再匹配index.json 发现没有 再匹配index.jsx 发现没有 再匹配index.css 发现有 就使用 都没找到会报错 只要找到一个就不会再往后面找了 所以文件名千万不要起一样 优点:简写路径 缺点:路径没有提示modules
:告诉 webpack 解析模块是去找哪个目录 默认是modules: ['node_modules']
他会先在当前目录下找node_modules 找不到再去上一级中找 再找不到再去上一级中找 一直往上找 如果我们目录层级比较深 这样找太麻烦了 所以我们可以通过绝对路径告诉它在哪里modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
如果resolve(__dirname, '../../node_modules')
没找到 会按照前面我们说的方式去找node_modules 这就是配置两个的原因- webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/[name].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [new HtmlWebpackPlugin()],
mode: 'development',
resolve: {
alias: {
$css: resolve(__dirname, 'src/css')
},
extensions: ['.js', '.json', '.jsx', '.css'],
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
}
};
devServer
- 用于开发环境
- webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/[name].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [new HtmlWebpackPlugin()],
mode: 'development',
resolve: {
alias: {
$css: resolve(__dirname, 'src/css')
},
extensions: ['.js', '.json', '.jsx', '.css'],
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
},
devServer: {
// 运行代码的目录
contentBase: resolve(__dirname, 'build'),
// 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload重载
watchContentBase: true,
watchOptions: {
// 忽略文件 只监视源代码
ignored: /node_modules/
},
// 启动gzip压缩
compress: true,
// 端口号
port: 5000,
// 域名
host: 'localhost',
// 自动打开浏览器
open: true,
// 开启HMR功能
hot: true,
// 不要显示启动服务器日志信息
clientLogLevel: 'none',
// 除了一些基本启动信息以外,其他内容都不要显示
quiet: true,
// 如果出错了,不要全屏提示~
overlay: false,
// 服务器代理 --> 解决开发环境跨域问题
// 正常情况下 浏览器和服务器通信存在跨域问题 但服务器与服务器之间没有跨域 我们的代码通过代理服务器运行
// 所以浏览器和代理服务器之间没有跨域问题 浏览器把请求放在代理服务器上 代理服务器替你转发到另一个服务器上
// 而服务器与服务器之间没有跨域 所以请求成功 代理服务器再把服务器的响应响应给浏览器 从而解决开发环境下的跨域问题
proxy: {
// 一旦devServer(端口号5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器(端口号3000)
'/api': {
target: 'http://localhost:3000',
// 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api)
pathRewrite: {
// 正则
'^/api': ''
}
}
}
}
};
optimization
webpack.config.js中代码如下
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
chunkFilename: 'js/[name].[contenthash:10]_chunk.js'
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [new HtmlWebpackPlugin()],
mode: 'production',
resolve: {
alias: {
$css: resolve(__dirname, 'src/css')
},
extensions: ['.js', '.json', '.jsx', '.css'],
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
},
optimization: {
splitChunks: {
chunks: 'all'
// 下面是默认值,一般不写~
/* minSize: 30 * 1024, // 分割的chunk最小为30kb 小于30kb不分割 大于30kb才分割
maxSize: 0, // 最大没有限制
minChunks: 1, // 要提取的chunk最少被引用1次 不引用直接别打包了
maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
maxInitialRequests: 3, // 入口js文件最大并行请求数量
automaticNameDelimiter: '~', // 名称连接符
name: true, // 可以使用命名规则
cacheGroups: {
// 分割chunk的组
// node_modules中的文件会被打包到 vendors 组的chunk中。--> 命名规则:vendors~xxx.js xxx是模块名称
// 满足上面的公共规则,如:大小超过30kb,至少被引用一次。
vendors: {
test: /[\\/]node_modules[\\/]/,
// 打包的优先级
priority: -10
},
default: {
// 要提取的chunk最少被引用2次
minChunks: 2,
// 优先级
priority: -20,
// 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
reuseExistingChunk: true
}
}*/
},
// 将当前模块的记录其他模块的hash单独打包为一个文件 runtime
// 解决:修改a文件导致b文件的contenthash变化
/*
b文件引用了a文件 改动了a文件 a文件的hash值发生变化 因为b文件引用了a文件 所以b文件中保留了a文件的hash值
但是现在由于改动了a文件 a文件的hash值变了 所以b文件的内容也变了(b引用了a文件的hash值 这个hash值变了)
所以b也要重新打包 为了解决这个问题 我们可以使用runtime将b文件中引用的a文件的hash值提取出来打包在其他文件里
*/
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
// 这样 当a文件变化时 只有a文件和a文件的runtime文件会重新打包 其他不会变
},
minimizer: [
// 配置生产环境的压缩方案:js和css
// 之前我们压缩js css用的是UglifyJsPlugin插件 但这个插件已经不维护了
// 以后我们压缩js css用Terser 如果你使用默认配置 可以不用管
// 在终端输入 npm i terser-webpack-plugin -D 下载terser-webpack-plugin
// 下面更改Terser默认配置
new TerserWebpackPlugin({
// 开启缓存
cache: true,
// 开启多进程打包
parallel: true,
// 启动source-map
sourceMap: true
})
]
}
};