为什么使用webpack
在打包出现之前打包工具出现之前我们是如何在web中使用JavaScript的呢?
1.引用一些脚本本来存放每个功能;此解决方案很难扩展,因为加载太多脚本会导致网络瓶颈
2.使用一个包含所有项目代码的大型 .js 文件,但是这会导致作用域、文件大小、可读性和可维护性方面的问题
3.面对以上的问题立即调用函数表达式诞生了(IIFE) - Immediately invoked function expressions
IIFE 解决大型项目的作用域问题;当脚本文件被封装在 IIFE 内部时,你可以安全地拼接或安全地组合所有文件,而不必担心作用域冲突。但是,修改一个文件意味着必须重新构建整个文件。
4.CommonJS 问世并引入了 require 机制,它允许你在当前文件中加载和使用某个模块。导入需要的每个模块,这一开箱即用的功能,帮助我们解决了作用域问题
5.虽然 CommonJS 是 Node.js 项目的绝佳解决方案,但浏览器不支持模块此时出现了ESM
是否可以有一种方式,不仅可以让我们编写模块,而且还支持任何模块格式并且可以同时处理资源和资产?这时出现了webpack。它是一个工具,可以打包你的 JavaScript 应用程序(支持 ESM 和 CommonJS),可以扩展为支持许多不同的资产,例如:images, fonts 和 stylesheets。
-模块化
-后端模块化
-common.js:node.js
-前端模块化
-AMD:require.js
-CMD: sea.js
-UMD: 同构的一些库中使用的方案
-ESM: ECMAscript Model
起步
1.创建一个空目录
npm init -y
npm install webpack webpack-cli webpack-dev-server --save-dev
执行完命令之后生成node_modules、package.json
以vue为例搭建一个简单的vue2+webpack5的项目
基础目录 创建以下文件以及文件夹
—— src(项目源代码目录)
—— App.vue(项目根组件)
—— main.js(项目打包入口文件)
—— views(页面组件)
—— assets(图片资源目录,存放图片)
—— components(公共组件目录)
—— child.vue(子组件)
—— styles(公共样式目录,存放公共,全局等一些样式表)
—— index.html(html模板)
—— webpack.config.js(webpack配置文件)
—— package.json(包管理文件)
// app.vue
<!--App.vue-->
<template>
<div id="app">
<Child></Child>
</div>
</template>
<script>
import Child from "@/components/child";
export default {
name: "App",
data() {
return {};
},
components: {
Child
}
};
</script>
<style scoped>
</style>
// main.js
import Vue from 'vue'
import App from './views/App'
new Vue({
el:'#app',
render: h => h(App)
})
配置webpack
1.通过js文件描述配置 如webpack.comfig.js
2.执行webpack可假如命令行参数 如webpack --devtool source-map
这俩种方式可以搭配使用,这种方式也是现在最常用的
开始配置webpack
在项目跟目录创建webpack.config.js
// webpack.config.js
module.exports = {
entry: "./src/main.js"
// 项目打包的入口,必填项
output: {
filename: "[name].[hash].js", //导出的文件名,hash值是避免文件名缓存
path: path.resolve(__dirname, "dist") //导出的打包后文件输出路径(必须是绝对路径)
},
}
安装依赖
npm i -S vue
//安装vue
npm i -D vue-loader
//.vue的组件会解析成template、script、style然后再通过其他配置解析成浏览器可识别的html、js、css文件
npm i -D vue-template-compiler
//把 vue-loader 提取出的 HTML 模版编译成对应的可执行的 JavaScript 代码
npm i -D vue-style-loader
//功能类似于 style-loader,是将 css-loader 加载后的 css作为样式标签动态注入到文档中,是专门应用于 vue 模板环境下的样式表加载器。因此如果配置了 vue-style-loader 就不需要再配置 style-loader了
npm i -D css-loader
// 解析 @import 和 url() ,会 import/require() 后再解析它们。通俗的讲就是 css-loader 会找出当前 css 文件中的 @import 和 url() 这样的导入语句,告诉 Webpack 依赖这些资源,然后同时将当前 css文件 解析成模块,处理完后再把结果交给 style-loader 去处理
npm i -D sass-loader
// 浏览器不能直接解析sass文件,sass-loader把sass解析为css 需要配置node-sass或者dart-sass
npm i -D url-loader
Resolve webpack内置的JavaScript模块化语法解析功能
// webpack.config.js
...
module.exports = {
resolve: {
extensions: [".js", ".vue", ".scss", ".css"], //后缀名自动补全,当导入文件的时候可以省略后缀名不写
alias: {
"@": path.resolve(__dirname, "../src"), //用@代替./src路径 所以就可以 import xx from ' @/xx'
}
}
}
合理使用 resolve.extensions
在导入语句没带文件后缀时,webpack 会自动带上后缀后去尝试询问文件是否存在,查询的顺序是按照我们配置 的 resolve.extensions 顺序从前到后查找,webpack 默认支持的后缀是 js 与 json。
如果我们配置 resolve.extensions= ['js', 'json'],那么 webpack 会先找 xxx.js
如果没有则再查找 xxx.json,所以我们应该把常用到的文件后缀写在前面,或者 我们导入模块时,尽量带上文件后缀名
loader(模块转换器)
将各个模块、非js文件解析为可识别的js文件
module:模块
rules:模块的规则
test:匹配规则
include:指定去查找的目录
exclude:排除的目录
use:配置应用的loader
loader:模块解析器
配置loader
const path = require("path");
module.exports = {
entry: "./src/index.js", //指定入口文件
output: {
filename: "[name].[hash].js", //打包导出文件的文件名
path: path.resolve(__dirname, "dist") //导出的打包后文件输出路径(必须是绝对路径)
},
module: {
rules: [
{
test: /\.css$/,//指定应用当前loader的类型文件,这里就是所有.css后缀的文件都应用css-loader来解析
use: [ "vue-style-loader", "css-loader"]
},
{
test: /\.s[ac]ss$/i,
use: ["vue-style-loader","css-loader","sass-loader"]
},
{
test: /\.(png|jpg|gif|svg|bmp)$/,
use: {
loader: "url-loader",
options: {
esModule: false,
limit: 10 * 1024, //限制图片资源大小,小于10kb的图片会以 base64 编码输出,大于此限制值的会以拷贝方式(file-loader)放到 'outputPath'指定目录下
//不能和file-loader一起使用
outputPath: "imgs/" //指定图片资源输出路径,不指定默认直接放到dist目录下,此时这里是 dist/imgs/
}
}
},
{
test: /\.js$/,
loader: "babel-loader",
exclude: /node_modules/ //不查找 'node_modules'目录
},
{
test: /\.vue$/,
use: "vue-loader"
}
]
}
};
Plugin(插件)
拓展webpack的功能,在打包过程中有一些事情loader是无法实现的,插件可以完美解决。格式是数组,数组中的每一项都是用来运行不同的插件实例(new xxxx)
npm install -D vue-loader
//vue-loader-plugin 注意安装vue-loader就带有这个插件不用再次安装将loader中定义的规则解析完成后对应到.vue文件中。例如/\.js$/ 的规则,那么它会应用到 .vue 文件里的 <script> 块
npm install -D html-webpack-plugin
/* 使用此插件会自动生成一个HTML文件,其中依赖自动注入。
teplate:使用定义好一个html模板
filename:打包后,dist目录下的html名字
hash:避免文件缓存*/
npm install -D mini-css-extract-plugin
//分离css代码,css通过css-loader、sass-loader解析后都会放在一个js文件,随着代码增多js文件会越来越大,加载速度也会越来越慢。通过此插件将css分离出来,并且能支持异步加载、按需加载。此插件在plugins注册还要在loader中修改
//webpack.config.js
const path = require("path"); //node.js 里面的path模块,这里用来解析绝对路径的
const HtmlWebpackPlugin = require("html-webpack-plugin"); //自动生成html
const VueLoaderPlugin = require("vue-loader/lib/plugin"); //必须导入此插件,它负责克隆您定义的任何其他规则,并将它们应用于.vue文件中的相应语言块
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); //分离css
module.exports = {
entry: "./src/main.js", //入口文件
output: {
filename: "[name].[hash].js", //导出的文件名,hash值是避免文件名缓存
path: path.resolve(__dirname, "dist") //导出的打包后文件输出路径(必须是绝对路径)
},
module: {
rules: [
{
test: /\.css$/,//指定应用当前loader的类型文件,这里就是所有.css后缀的文件都应用css-loader来解析
use: [ {
loader: MiniCssExtractPlugin.loader //分离css
},, "css-loader"]
},
{
test: /\.s[ac]ss$/i,
use: [{
loader: MiniCssExtractPlugin.loader //分离css
}, "css-loader", "sass-loader"]
},
{
test: /\.(png|jpg|gif|svg|bmp)$/,
use: {
loader: "url-loader",
options: {
esModule: false,
limit: 10 * 1024, //限制图片资源大小,小于10kb的图片会以 base64 编码输出,大于此限制值的会以拷贝方式(file-loader)放到 'outputPath'指定目录下
outputPath: "imgs/" //指定图片资源输出路径,不指定默认直接放到dist目录下,此时这里是 dist/imgs/
}
}
},
{
test: /\.js$/,
loader: "babel-loader",
exclude: /node_modules/ //不查找 'node_modules'目录
},
{
test: /\.vue$/,
use: "vue-loader"
}
]
},
plugins:[
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: "./index.html", //模板文件
filename: "index.html", //文件名
hash: true //避免缓存
}),
new MiniCssExtractPlugin({
filename: "styles/[name].[hash].css", //指定分离生成的css文件名,hash为了避免缓存
})
]
}
如果我们想要在本地运行项目还需要以下准备
npm i -D webpack-dev-server
// DevServer会帮我们在本地启动一个服务,这样我们可以通过在浏览器访问服务地址来预览效果,而不用每次手动去打开 index.html 文件
npm i -D progress-bar-webpack-plugin
//显示打包进度,当项目大了不用傻傻的等待却不知道打包了多少了
//webpack.config.js
module.exports = {
...
devServer: {
host: 'localhost',//开发服务器监听的主机地址
port: 8000, //开发服务器监听的端口号,默认是 8080
compress: true, //启用压缩
host: true, // 开启热更新
open: true, // 自动使用默认浏览器打开
},
plugins:[
...
new webpack.HotModuleReplacementPlugin() //需要引入webpack启用热替换模块(HMR)的
]
};
随着项目的开发配置项会越来越多,所有的配置肯定不能都写在一个文件中,这样可读性不高每次修改起来也会很麻烦。接下来我们来第二弹配wepack,将来也会第三弹第四弹,不停的优化下去。
首先区分一下开发环境(development)和生产环境(production )在根目录下创建build文件夹,在文件夹中新建三个文件:webpack.base.conf.js(基础配置文件)、webpack.dev.conf.js(开发环境配置文件)、webpack.pro.conf.js(生产环境配置文件)
区分配置
webpack.base.conf.js
先将整个webpack.config.js代码复制进去。去除webpack-dev-server相关代码,去除mode、devtool等配置项
安装copy-webpack-plugin
npm install -D copy-webpack-plugin
// 将静态文件打包的项目中这个插件就是采用拷贝的方式来将其复制进我们的打包目录
from: 需要拷贝的静态文件源路径
to: 拷贝后输出的目标位置
上代码
//基础配置文件
const path = require("path");
const VueLoaderPlugin = require("vue-loader/lib/plugin"); //必须导入此插件,它负责克隆定义的任何其他规则,并将它们应用于.vue文件中的相应语言块
const CopyPlugin = require("copy-webpack-plugin");//拷贝静态文件,例如.txt,.md等文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");//分离css
const ProgressBarPlugin = require("progress-bar-webpack-plugin"); //运行/打包,显示进度条
module.exports = {
entry: path.resolve(__dirname, "../src/main.js"), //指定入口文件
output: {
filename: "[name].[hash].js", //导出的文件名
path: path.resolve(__dirname, "../dist") //导出的打包后文件输出路径(必须是绝对路径)
},
resolve: {
extensions: [".js", ".vue", ".scss", ".css"], //后缀名自动补全,当导入文件的时候可以省略后缀名不写
alias: {
"@": path.resolve(__dirname, "../src"), //用@代替./src路径 所以就可以 import xx from ' @/xx'
assets: path.resolve(__dirname, "../src/assets")
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
"css-loader"
]
},
{
test: /\.s[ac]ss$/i,
use: [
{
loader: MiniCssExtractPlugin.loader
},
"css-loader",
"sass-loader"
]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: "url-loader",
options: {
esModule: false,
limit: 10 * 1024, //限制图片资源大小,小于10kb的图片会以 base64 编码输出,大于的会以拷贝方式(file-loader)放到 'outputPath'指定目录下
outputPath: "static/imgs/" //指定图片资源输入路径,不指定默认直接放到dist目录下,此时这里是 dist/static/imgs/
}
}
},
{
test: /\.js$/,
loader: "babel-loader",
exclude: /node_modules/ //不查找 'node_modules'目录
},
{
test: /\.vue$/,
use: "vue-loader"
}
]
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: "static/css/[name].[hash].css"
}),
new ProgressBarPlugin(),
new CopyPlugin({
patterns:[
{
from: path.resolve(__dirname, "../src/static"),
to: "static"
}
]
})
]
};
webpack.dev.conf.js
安装 friendly-errors-webpack-plugin
npm install -D friendly-errors-webpack-plugin
// 去掉项目开发中一些无关紧要的console信息在webpackdevsever配置项中增加overlay: true显示错误信息
上代码
//开发环境配置文件
const path = require("path");
const webpack = require("webpack");
const {merge} = require("webpack-merge");//合并配置项的方法
const base = require("./webpack.base.conf");//基础配置项
const HtmlWebpackPlugin = require("html-webpack-plugin"); //自动生成html
const FriendlyErrorsPlugin = require("friendly-errors-webpack-plugin"); //清理webpack打包过程中无用信息overlay: true, // 编译出现错误时,将错误直接显示在页面上
const os = require('os');
function getNetworkIp() {
let needHost = ''; // 打开的host
try {
// 获得网络接口列表
let network = os.networkInterfaces();
for (let dev in network) {
let iface = network[dev];
for (let i = 0; i < iface.length; i++) {
let alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
needHost = alias.address;
}
}
}
} catch (e) {
needHost = 'localhost';
}
return needHost;
}
const devWebpackConfig = merge(base, {
mode: "development",// 开发模式
devtool: "source-map",// 代码映射
devServer: {
host: "localhost", //用于配置 DevServer 服务监听的地址,想要局域网中的其它设备访问你本地的服务,请配置为 0.0.0.0,默认只有本地可以访问
port: 8000, //访问端口号,默认8080,如果被占用,会自动更换
quiet: true, // FriendlyErrorsPlugin的固定用法
compress: true, //启用压缩
open: true, //自动打开浏览器
overlay: true, // 编译出现错误时,将错误直接显示在页面上
},
plugins: [
new webpack.HotModuleReplacementPlugin(), //模块热替换,当更新代码时候自动编译,无需手动更新,一般配合webpack-dev-server 使用
new HtmlWebpackPlugin({
template: "./index.html", //模板文件
filename: "index.html", //文件名
inject: true // true || 'head' || 'body' || false,注入资源到给定的html模板文件,如果为true或者body,则将所有js文件都将放置在body元素的底部
})
]
});
module.exports = new Promise(resolve => {
const host = devWebpackConfig.devServer.host;
const port = devWebpackConfig.devServer.port;
devWebpackConfig.plugins.push(
new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application local is running here: http://${host}:${port}`, `Your application is running here: http://${getNetworkIp()}:${port}`],
},
onErrors: function(severity, errors) {
// You can listen to errors transformed and prioritized by the plugin
// severity can be 'error' or 'warning'
}
})
);
resolve(devWebpackConfig);
});
修改我们的启动命令
"dev": "webpack-dev-server --config ./build/webpack.dev.conf.js",
webpack.pro.conf.js
放置生产打包的配置
npm install -D terser-webpack-plugin
//用来压缩js代码,放弃uglifyjs-webpack-plugin的原因是支持es6语法
npm install -D optimize-css-assets-webpack-plugin
//用来压缩css代码
npm install -D clean-webpack-plugin
//每次打包清理dist文件夹
//生产环境配置文件
const path = require("path");
const {merge} = require("webpack-merge"); //合并webpack options
const base = require("./webpack.base.conf");
const HtmlWebpackPlugin = require("html-webpack-plugin"); //自动生成html
const TerserPlugin = require("terser-webpack-plugin"); //压缩js
const OptimizeCSSPlugin = require("optimize-css-assets-webpack-plugin");//压缩css
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); //每次打包前,先清理掉之前的打包文件
module.exports = merge(base, {
mode: "production",
optimization: {
minimize: true, //告知 webpack 使用 TerserPlugin 压缩 打包后的js文件
minimizer: [new TerserPlugin()] //允许通过提供一个或多个定制过的 TerserPlugin 实例,覆盖默认压缩工具(minimizer)。
},
plugins: [
new HtmlWebpackPlugin({
template: "./index.html", //模板文件
filename: "index.html", //文件名
inject: true, // true || 'head' || 'body' || false,注入资源到给定的html模板文件,如果为true或者body,则将所有js文件都将放置在body元素的底部
favicon: path.resolve(__dirname,'../favicon.ico'),
//如果将minify选项设置为true(当webpack的模式为“生产”时为默认值),则将以下选项来缩小生成的HTML(压缩html资源)
minify: {
removeComments: true, //移除注释
collapseWhitespace: true, //合并空格
removeAttributeQuotes: true //移除属性双引号
}
}),
new OptimizeCSSPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require("cssnano")
}),
new CleanWebpackPlugin()
]
});
如果环境多了怎么办呢
在node中,我们有一个对象process对象,它里面包括的一些信息,env和它的一些属性,当然NODE_ENV是我们自己加上去的自定义属性,用来区分环境变量,也就是通过这个变量来进行区别是开发环境还是生产环境;但是有个问题,不同电脑上设置的方式是不一样的,所以cross-env就来了,它可以跨平台设置环境和使用环境变量
npm install cross-env -D
在执行命令中增加
"build": "cross-env NODE_ENV=production webpack --config ./build/webpack.pro.conf.js",
"dev": "cross-env NODE_ENV=development webpack serve --config ./build/webpack.dev.conf.js"
const NODE_ENV=process.env.NODE_ENV; //获取当前环境 这样我们只需在启动命令的适配配置不同的环境就能开发了
console.log(NODE_ENV);
webpack同样也为我们提供了一些方法:在启动命中编辑 --node-env production //process.env.NODE_ENV = 'production' 同样会设置环境变量
注意:如果你不明确的设置 mode,mode 选项的值会被 --node-env 覆盖。例如 --node-env production 时,会把 process.env.NODE_ENV 和 mode 均设置为 'production'
第二弹 优化
**css3新特性 ** 兼容css3新特性需要增加不同浏览器厂商的前缀。这个时候用postcss-loader
npm install -D postcss-loader autoprefixer
创建一个postcss.config.js 这个文件目录需要放在根目录
// postcss.config.js
// 需要配置这个插件信息
module.exports = {
plugins: [
require('autoprefixer')({
overrideBrowserslist: [
"Android 4.1",
"iOS 7.1",
"Chrome > 31",
"ff > 31",
"ie >= 8"
]
})
]
};
在webpack中加上
test: /\.s[ac]ss$/i,
use: ['style-loader','css-loader','sass-loader','postcss-loader']
// 最后一个位置处加上postcss-loader
在浏览器中就能看到css3会加上厂商的前缀了
-webkit-xxx:
-ms-xxx:
xxx:
这样配置有些不足,我们在scss文件中又导入新的scss文件不会出现前缀,所以我们要这样配置
{
test: /\.s[ac]ss$/i,
use: ['style-loader',
{
loader: 'css-loader',
options:{
importLoaders:2,
modules : true
}
},
'postcss-loader',
'sass-loader'
]
}
import异步懒加载
当我需要按需引入某个模块时,这个时候,我们就可以使用懒加载,其实实现的方案就是import语法,在达到某个条件时,我们才会去请求资源
install @babel/plugin-syntax-dynamic-import -D
在.babelrc配置文件中,增加一个插件 对应的ui库方法也要修改对应的写法
{
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
删除无用css (暂时不生效)
使用 PurgeCSS 来完成对无用 css 的擦除,它需要和 mini-css-extract-plugin 配合使用
npm install purgecss-webpack-plugin -D
// webpack.base.conf.js
const PurgecssPlugin = require('purgecss-webpack-plugin');// 删除无用css
plugins:[
...
new PurgecssPlugin({
paths: glob.sync(`${path.join(__dirname, './src')}/**/*`, { nodir: true }),
}),
...
]
thread-loader打包加速
行在nodeJS上的webpack是单线程模型的,也就是说Webpack一个时刻只能处理一个任务,不能同时处理多个任务。thread-loader它将任务分解给多个子进程去并发执行,子进程处理完后再将结果发给主进程。
注意:如果小项目,文件不多,无需开启多进程打包,反而会变慢,因为开启进程是需要花费时间的。使用时,需将此 loader 放置在其他 loader 之前
npm install thread-loader -D
use: [
{
loader: "thread-loader",
// 有同样配置的 loader 会共享一个 worker 池
options: {
// 产生的 worker 的数量,默认是 (cpu 核心数 - 1),或者,
// 在 require('os').cpus() 是 undefined 时回退至 1
workers: 2,
// 一个 worker 进程中并行执行工作的数量
// 默认为 20
workerParallelJobs: 50,
// 额外的 node.js 参数
workerNodeArgs: ['--max-old-space-size=1024'],
// 允许重新生成一个僵死的 work 池
// 这个过程会降低整体编译速度
// 并且开发环境应该设置为 false
poolRespawn: false,
// 闲置时定时删除 worker 进程
// 默认为 500(ms)
// 可以设置为无穷大,这样在监视模式(--watch)下可以保持 worker 持续存在
poolTimeout: 2000,
// 池分配给 worker 的工作数量
// 默认为 200
// 降低这个数值会降低总体的效率,但是会提升工作分布更均一
poolParallelJobs: 50,
// 池的名称
// 可以修改名称来创建其余选项都一样的池
name: "my-pool"
},
},
// 耗时的 loader(例如 babel-loader)
];
预警
可以通过预警 worker 池来防止启动 worker 时的高延时。
这会启动池内最大数量的 worker 并把指定的模块加载到 node.js 的模块缓存中。
const threadLoader = require('thread-loader');
threadLoader.warmup(
{
// 池选项,例如传递给 loader 选项
// 必须匹配 loader 选项才能启动正确的池
},
[
// 加载模块
// 可以是任意模块,例如
'babel-loader',
'babel-preset-es2015',
'sass-loader',
]
);
全局读取变量
DefinePlugin 允许在 编译时 创建配置的全局常量,这在需要区分开发模式与生产模式进行不同的操作时,非常有用。
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify('1.0'),
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
});
我们可以直接在对应的环境中配置,但是这样配置项一多了这样写难免有点麻烦。 解决:在根目录下创建config文件夹创建俩个文件dev.env.js、prod.env.js
//dev.env.js
module.exports = {
NODE_ENV: '"development"',
NODE_CONFIG:'"development"'
...
}
//webpack.dev.conf.js
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
图片压缩
使用image-minimizer-webpack-plugin对图片优化
ImageMinimizer
npm install image-minimizer-webpack-plugin -D
官网推荐的无损插件有
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D --ignore-scripts
官网推荐的有损插件有
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D --ignore-scripts
预先编译资源模块(DllPlugin)
我们在打包的时候一版依赖模块、第三方模块是不会改变的,所以我们想只有第一次打包的时候去打包一下,当我们后边直接拿来用就可以。
创建配置文件
在build文件夹中新建webpack.dll.js
// config/webpack.dll.js
const path = require('path');
const webpack = require('webpack');
const TerserPlugin = require("terser-webpack-plugin"); //压缩js
module.exports = {
mode: 'production', // 环境
entry: {
vendors: ['lodash','vue'] // 将 lodash\vue 打包到 vendors.js
},
optimization: {
minimize: true, //告知 webpack 使用 TerserPlugin 压缩 打包后的js文件
minimizer: [new TerserPlugin()] //允许通过提供一个或多个定制过的 TerserPlugin 实例,覆盖默认压缩工具(minimizer)。
},
output: {
filename: '[name].dll.js', // 输出的名字
path: path.resolve(__dirname, '../dll'), // 输出的文件目录
library: '[name]' // 将我们打包出来的文件以全部变量的形式暴露,可以在浏览器变量的名字进行访问
},
plugins: [
// 对生成的库文件进行分析,生成库文件与业务文件的映射关系,将结果放在 mainfest.json 文件中
new webpack.DllPlugin({
name: '[name]', // 和上面的 output中library 输出的名字要相同
path: path.resolve(__dirname, '../dll/[name].manifest.json'),
})
]
}
增加启动命令
"dll": "webpack --config ./build/webpack.dll.config.js"
2.修改基本配置文件,将我们生成的dll导入到html中。这里我们借助add-asset-html-webpack-plugin,同时我们需要使用 webpack 自带的 DllReferencePlugin 插件对 mainfest.json 映射文件进行分析。
<!-- 手动引入 -->
<script src="<%= htmlWebpackPlugin.options.path %>static/vendor/vendor.dll.js"></script>
自动引入
//自动引用到html
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
plugins:[
...
// 有几个 dll.js,这里就响应 new 几个 webpack.DllReferencePlugin
new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(path.resolve(__dirname, '../dll/vendors.manifest.json'))
}),
// 将 dll 注入到 生成的 html 模板中
new AddAssetHtmlPlugin({
// dll文件位置
filepath: path.resolve(__dirname, '../dll/*.js'),
// dll 引用路径
publicPath: './static/vendor',
// dll最终输出的目录
outputPath: './static/vendor'
})
]
缺点:配置复杂用hard-source-webpack-plugin即可
开启缓存加速
- babel-loader
- terser-webpack-plugin
- hard-source-webpack-plugin 以下最好在基础配置文件配置
// ...
module: {
rules: [
{
test: /\.jsx?$/,
// exclude: /node_modules/,
// include: path.resolve(__dirname, '../src'),
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
}
},
]
},
]
}
// ...
const TerserPlugin = require('terser-webpack-plugin');
// ...
const commonConfig = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
cache: true// 开启缓存
}),
],
},
// ...
}
hard-source-webpack-plugin
npm install -D hard-source-webpack-plugin
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
// ...
const plugins = [
// ...
new HardSourceWebpackPlugin(),
];
// ...
开启gzip压缩
我们资源请求的时候,如果文件过大而且多的情况下,会导致网络请求耗时。严重阻塞后面的进程。
我们使用gzip对资源文件进行压缩
npm install -D compression-webpack-plugin
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
...
plugins: [
...
new CompressionPlugin({
algorithm: 'gzip', // 使用gzip压缩
test: /\.js$|\.html$|\.css$/, // 匹配文件名
filename: '[name].gz', // 压缩后的文件名(保持原文件名,后缀加.gz)
minRatio: 1, // 压缩率小于1才会压缩
threshold: 10240, // 对超过10k的数据压缩
deleteOriginalAssets: false, // 是否删除未压缩的源文件,谨慎设置,如果希望提供非gzip的资源,可不设置或者设置为false(比如删除打包后的gz后还可以加载到原始资源文件)
}),
],
},
};
这里对js、html、css文件进行了压缩处理,并没有进行图片压缩,因为图片压缩并不能实际减少文件大小,反而会导致打包后生成很多同大小的gz文件,得不偿失。
此时我们还需要注意在在服务器配置返回对应的压缩文件。
以nginx为例
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
gzip_static on;
server {
listen 9090;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root d:/BJCA;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# location =/compApi/ {
# proxy_pass http://192.168.118.70:10818/;
# }
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
# location ~ \.php$ {
# proxy_pass http://127.0.0.1;
# }
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
# location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
# }
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
}
静态加载gz文件主要是依托于下面的请求头:
Accept-Encoding: gzip, deflate, br
当我们的请求头出现了这个说明成功了。
服务器在线gzip压缩
前端不用进行任何配置,也不用webpack生成gz文件,服务器进行处理,拿nginx举例:
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
# 开启gzip
gzip on;
# 设置缓冲区大小
gzip_buffers 4 16k;
#压缩级别官网建议是6
gzip_comp_level 6;
#压缩的类型
gzip_types text/plain application/javascript text/css application/xml text/javascript application/x-httpd-php;
server {
listen 8462;
server_name localhost;
location / {
root dist;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
两种方案的问题:
- webpack打包,然后直接使用静态的gz,缺点就是打包后文件体积太大,但是不耗服务器性能;
- 使用nginx在线gzip,缺点就是耗性能,需要实时压缩,但是vue打包后的文件体积小。
俩种方法都兼容的写法
gzip on;
gzip_static on;
gzip_comp_level 2;
gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
首先,gzip_static的优先级高,会先加载静态gz文件,当同目录下不存在此文件的时候,会执行在线压缩的命令。
区分静态加载还是在线压缩
响应头的Content-Edcoding:gzip表示gzip压缩已经生效,而Etag中只有简单字符表示静态资源加载,而前面带 W/ 表示启动了在线压缩。
eslint
npm i eslint eslint-webpack-plugin eslint-config-airbnb-base eslint-plugin-import eslint-plugin-vue-libs -D
- eslint 这个必须安装的
- eslint-webpack-plugin(webpack v5) 用于替代eslint-loader(webapck v4及以下)
- eslint-config-airbnb-base 支持es6+的语法规范 就不用每一项都去配置了
- eslint-plugin-import 用于在webpack.json中读取eslintConfig配置项
- eslint-import-resolver-webpack(如果在webpack.config.js中配置了alias 并且在import时使用了别名需要安装这个)
const ESLintPlugin = require('eslint-webpack-plugin');
plugins: [
...
new ESLintPlugin({
fix: true,
extensions: ['js', 'json', 'vue'],
exclude: '/node_modules/'
}),
...
]