Webpack5 基础 & Vue3 项目搭建
一、概念
官网解释: webpack 是一个用于现代
JavaScript
应用程序的__静态模块打包工具__。当webpack
处理应用程序时,它会在内部构建一个 依赖图(dependency graph) ,此依赖图对应映射到项目所需的每个模块,并生成一个或多个bundle
。
二、起步
创建 webpack-demo-main 文件夹。
cd webpack-demo-main // 进入文件目录
npm init -y // npm初始化 -y的意思是一路yes。省去了回车步骤
npm install webpack webpack-cli --save-dev
打开项目目录下的 package.json 文件,展开此文件如下所示:
{
"name": "webpack-demo-main",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
在该文件中,我们新增 private: true
参数,该参数是确保私有。移除掉 main 参数。
现在,我们将创建以下目录结构、文件和内容:
webpack-demo-main
|- package.json
+ |- index.html
+ |- /src
+ |- index.js
index.js
//index.js
function comp() {
const el = document.createElement('div');
el.innerHTML = '<i>你好, X1AXX1A</i>'
return el;
}
document.body.appendChild(comp());
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>X1AXX1A</title>
</head>
<body>
<script src="./src/index.js"></script>
</body>
</html>
三、管理资源
3.1 配置入口与输出
在项目根目录创建 webpack.config.js 文件,并输入以下代码:
//webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',//development开发环境,production生产模式
entry: './src/index.js',//入口文件
output: {
filename: '[name].[contenthash].js',// 输出文件
path: path.resolve(__dirname, 'dist'),// 输出文件存放地址
},
};
entry
属性可以设置项目的入口文件
output
指示 webpack 如何去输出、以及在哪里输出你的「bundle、asset 和其他你所打包或使用 webpack 载入的任何内容」。
|-filename
设置输出的文件名
|-path
输出文件存放目录。
在Node.js
中,__dirname
总是指向 被执行的js文件 的绝对路径。
我们在 webpack.config.js 文件中打印一下 __dirname
看看,这就是当前的 webpack.config.js 所在的绝对路径了。
关于 path.resolve
方法会将路径或路径片段的序列解析为绝对路径。给定的路径序列会从右到左进行处理,后面的每个 path
会被追加到前面,直到构造出绝对路径。
path.resolve('/目录1/目录2', './目录3');
// 返回: '/目录1/目录2/目录3'
path.resolve('/目录1/目录2', '/目录3/目录4/');
// 返回: '/目录3/目录4'
而 path.join
方法会将所有给定的 path
片段连接到一起(使用平台特定的分隔符作为定界符),然后规范化生成的路径。如果连接后的路径字符串为长度为零的字符串,则返回 '.',表示当前工作目录。
path.join('/目录1', '目录2', '目录3/目录4', '目录5', '..');
// 返回: '/目录1/目录2/目录3/目录4'
我们将新建 dist 目录,将 index.html 移到 dist 目录下,并改一下 script 引入路径
//index.html
//<script src="./src/index.js"></script>
<script src="main.js"></script>
package.json 文件中 scripts 字段内定义脚本命令。
//...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
//...
然后执行
npm run build
dist 文件夹中就多出了一个 main.js 文件。
3.2 加载css
为了在 JavaScript 模块中 import
一个CSS文件,你需要安装 style-loader
和 css-loader
,并在module
配置中添加这些 loader
。
npm install --save-dev style-loader css-loader
然后在 webpack.config.js 中添加如下代码:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
},
};
模块 loader
可以链式调用。链中的每个 loader
都将对资源进行转换。链会逆序执行。第一个 loader
将其结果(被转换后的资源)传递给下一个 loader
,依此类推。最后,webpack 期望链中的最后的 loader
返回 JavaScript。
所以应保证 loader 的先后顺序:style-loader
在前,而 css-loader
在后。如果不遵守此约定,webpack 可能会抛出错误。
css-loader
会处理import
require()
@import
url
引入的内容,将其转化成js对象。
我们先将 style-loader
去掉,做个测试,在index.js中尝试先 import
一个 CSS 文件然后打印出来,我们打印出来的 style 被 css-loader
转成了js数组了。如下所示:
修改 index.js
import style from './index.css'
function comp() {
const el = document.createElement('div');
el.innerHTML = '<i>你好, X1AXX1A</i>'
console.log(style)
return el;
}
document.body.appendChild(comp());
同目录新增 index.css 文件
div {
color: red
}
因为 style 打印出来的是个数组,页面是无法直接使用的,可以看到字体没有变红色。
接下来我们将 style-loader
加回去。重新运行,字体变红色了。
所以 style-loader
主要是将 css-loader
生成的数组加到js中。
3.3 加载图片
webpack 5
可以使用内置的Asset Modules,它允许使用资源文件(字体,图标等)而无需配置额外 loader
。
webpack 5 之前,通常使用的:
raw-loader
将文件导入为字符串url-loader
将文件作为 data URI 内联到 bundle 中file-loader
将文件发送到输出目录 资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader。asset/resource
发送一个单独的文件并导出 URL。之前通过使用file-loader
实现。asset/inline
导出一个资源的 data URI。之前通过使用url-loader
实现。asset/source
导出资源的源代码。之前通过使用raw-loader
实现。asset
在导出一个 data URI 和发送一个单独的文件之间 自动选择 。之前通过使用url-loader
,并且配置资源体积限制实现。 所以我们只需要新增如下代码,即可。
{
test: /\.(jpe?g|png|gif|svg)$/i,
type: 'asset',//在导出一个 data URI 和发送一个单独的文件之间自动选择
}
3.4 解析(resolve)
常见有alias
、extensions
resolve.alias
创建 import
或 require
的别名,来确保模块引入变得更简单。例如,一些位于 src/
文件夹下的常用模块
在 src 下创建 assets 目录,存放一个图片,然后在 index.js 中导入一个图片的时候可以这么写。
import imgSrc from '@/assets/img.png'
resolve.extentions 尝试按顺序解析这些后缀名,即导入文件的时候不需要写后缀名,webpack 会按配置的后缀名顺序解析找到对应文件。
module.exports = {
//...
resolve: {
extensions: ['.vue', '.js'], //表示在import 文件时文件后缀名可以不写
alias: {
'@': path.join(__dirname, 'src')
// 这里的别名配置需与 jsconfig 中的 paths 别名一致
// import的文件在src下component里的时候可以直接写成 @/component/...
}
},
//...
}
但是使用 alias 的时候,可能会发现路径和函数的智能提示不见了,如果路径名称很复杂的话很容易写错而且也不方便。
这时候可以在根目录新增jsconfig.json
{
"compilerOptions": {
"baseUrl": "./", // 基本目录,用于解析非相对模块名称
"paths": {
"@/*": ["src/*"] // 指定要相对于 baseUrl 选项计算别名的路径映射
},
"experimentalDecorators": true //为ES装饰器提案提供实验支持
},
"exclude": ["node_module", "dist"]
}
注意:若项目集成 TypeScript 的时候,配置文件则是 tsconfig.json,设置 allowJs: true,jsconfig.json 才生效。
3.5 将ES6+ 转 ES5
为兼容广大浏览器,需要将js语法解析成ES5版本的。这时候就第三方的 loader
即 Babel
来帮助 webpack 来处理 ES6+ 语法。
3.5.1 Babel是什么呢?
Babel 是一个 JavaScript 编译器 Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
babel-loader
允许你使用 Babel
和 webpack
转译 JavaScript
文件。
安装相关依赖包
npm i -D babel-loader @babel/preset-env @babel/core
关于@
, babel 7
的一大调整,原来的 babel-xx
包统一迁移到 babel
域下
@babel-core
Babel 的核心功能包含在 @babel/core 模块中@babel/preset-env
为目标浏览器中没有的功能加载转换插件 webpack.config.js
//...
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader']
}
]
//...
在根目录创建.babelrc
文件
{
"presets": [
["@babel/preset-env"]
]
}
index.js 中添加一下代码
// Babel 测试
console.log([1,2,3].findIndex(x => x === 4))
console.log('abc'.padStart(10))
箭头函数被转换了,说明是成功的。
@babel/preset-env
提供了一个 useBuiltIns
参数,设置值为 usage
时,就只会包含代码需要的 polyfill
。有一点需要注意:配置此参数的值为 usage
,必须要同时设置 corejs
(如果不设置,会给出警告,默认使用的是"corejs": 2) ,注意: 这里仍然需要安装 @babel/polyfill
(当前 @babel/polyfill
版本默认会安装 "corejs": 2)
首先说一下使用 core-js@3
的原因,core-js@2
分支中已经不会再添加新特性,新特性都会添加到 core-js@3
。例如你使用了 Array.prototype.flat()
,如果你使用的是 core-js@2
,那么其不包含此新特性。为了可以使用更多的新特性,建议大家使用 core-js@3
。
npm install --save @babel/polyfill core-js@3
//.babelrc
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3
}]
]
}
3.5.2 @babel/plugin-transform-runtime
Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend
/ _classCallCheck
。默认情况下会被添加到每一个需要它的文件中。这样会造成重复引入,@babel/plugin-transform-runtime
可以解决这种情况。
转换插件 @babel/plugin-transform-runtime
通常仅在开发中使用,但是运行时需要依赖 @babel/runtime
,所以 @babel/runtime
必须要作为生产依赖被安装。
npm i -D @babel/plugin-transform-runtime
npm i @babel/runtime //这里不带-D哦
在presets
后面添加一行配置
//.babelrc
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3
}]
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
避免全局污染,我们使用依赖 @babel/runtime-corejs3
,再修改一下配置文件
npm install @babel/runtime-corejs3 --save
//.babelrc
{
"presets": [
["@babel/preset-env"]
],
"plugins": [
["@babel/plugin-transform-runtime", {
"corejs": 3
}]
]
}
四、管理输出
在上面我们的测试例子中,生成的 main.js
需要我们手动的在 index.html
中引入。然而随着应用程序增长,并且一旦开始在文件名中使用 contenthash
并输出 多个 bundle
,如果继续手动管理 index.html
文件,就会变得困难起来。
这时候就需要用到 HtmlWebpackPlugin 啦
npm install --save-dev html-webpack-plugin
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
//...
plugins: [
new HtmlWebpackPlugin({})
],
module: {
//...
HtmlWebpackPlugin 会新生成 index.html 文件,替换我们的原有文件。
接下来我们在根目录新建 public
文件夹,将 dist
中的 index.html
移动到 public
文件夹下,并修改 webpack.config.js
文件
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
//...
plugins: [
new HtmlWebpackPlugin({
title: '我是webpack.config配置的标题',
template: './public/index.html',
//压缩HTML
minify: {
removeComments: true, // 移除HTML中的注释
collapseWhitespace: true // 删除空白符与换行符
}
})
],
module: {
//...
这样表示以 public
下的 index.html
为引用模板。动态的引入编译后的相关服务资源。若是在配置文件中配置 title
属性,就需要我们修改html文件中的 title
来获取该配置选项。如下所示:
<!--index.html-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= htmlWebpackPlugin.options.title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
</body>
</html>
由于 webpack
将生成文件并放置在 /dist
文件夹中,但是它不会追踪哪些文件是实际在项目中用到的,导致 dist
内有很多多余文件,这时就需要配合使用 clean-webpack-plugin 插件,创建前清空 dist
文件夹
npm install --save-dev clean-webpack-plugin
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
//...
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: '我是webpack.config配置的标题',
template: './public/index.html',
//压缩HTML
minify: {
removeComments: true, // 移除HTML中的注释
collapseWhitespace: true // 删除空白符与换行符
}
})
],
module: {
//...
五、开发环境
5.1 热更新
在每次编译代码时,手动运行 npm run build
会显得很麻烦。webpack-dev-server 插件正好解决这个问题。
webpack-dev-server 主要提供两个功能
- 为静态文件提供web服务
- 自动刷新和热替换(HMR) 自动刷新指当前修改webpack会进行自动编译,更新网页内容 热替换指运行时更新各种模块,即局部刷新
npm install --save-dev webpack-dev-server
webpack.config.js
module.exports = {
//...
devServer: {
contentBase: './dist'
},
//...
}
以上配置告知 webpack-dev-server
,将 dist
目录下的文件serve到 localhost:8080
下。(译注: serve
,将资源作为 server
的可访问文件)
webpack-dev-server
在编译之后不会写入到任何输出文件。而是将bundle
文件保留在内存中,然后将它们 serve 到server
中,就好像它们是挂载在server
根路径上的真实文件一样。如果你的页面希望在其他不同路径中找到 bundle 文件,则可以通过dev server
配置中的publicPath
选项进行修改。
修改package.json添加运行代码
{
//...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack serve --open chrome",
"build": "webpack"
},
//...
}
然后在控制台执行 npm run dev
即可看到实现了热更新,但是却类似F5刷新的那种更新。
我们要实现的热更新,添加一行
hot: true
即可
devServer: {
contentBase: './dist',
hot: true
},
5.2 代理配置
在日常开发过程中,经常遇到跨域问题,这里就可以用到 devServer
的 proxy
配置代理,来实现跨域请求。
webpack.config.js
module.exports = {
//...
devServer: {
proxy: {
'/api': 'http://localhost:3000',
},
},
};
假如现在,对 /api/users 的请求会将请求代理到 http://localhost:3000/api/users。
如果不希望传递/api,则需要重写路径:
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {
'^/api': ''
},
},
},
},
};
这时候,对 /api/users 的请求会将请求代理到http://localhost:3000/user
默认情况下,将不接受在 HTTPS
上运行且证书无效的后端服务器。 如果需要,可以这样修改配置:
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'https://other-server.example.com',
secure: false,
},
},
},
};
如果想将多个特定路径代理到同一目标,则可以使用一个或多个带有 context
属性的对象的数组:
module.exports = {
//...
devServer: {
proxy: [
{
context: ['/auth', '/api'],
target: 'http://localhost:3000',
},
],
},
};
默认情况下,代理时会保留主机头的来源,可以将 changeOrigin
设置为 true
以覆盖此行为。
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
},
},
},
};
更多教程可以查看官方文档
5.3 DevTool
为了更容易地追踪 error 和 warning,JavaScript 提供了 source maps
功能,可以将编译后的代码映射回原始源代码。devtool
控制是否生成,以及如何生成 source map
。不同的 devtool
设置,会导致性能差异。具体参照官方教程
先来试试 inline-source-map
,修改一下配置文件。
webpack.confog.js
module.exports = {
mode: 'development',
entry: './src/index.js',//入口文件
output: {
filename: '[name].[contenthash].js',// 输出文件
path: path.resolve(__dirname, 'dist'),// 输出文件存放地址
},
devtool: "inline-source-map",//这里添加一行
//...
然后我们在index.js中输入 console.llog('这是一个错误')
,然后打包项目。
打包的结果,生成一个 map 文件内容对应的 base64 插入到 main.bundle.js 里面去
试试
source-map
可以看到,多出了.map文件,main.bundle.js中末端有指明具体引用的map文件
比如有一个场景是我们需要调试一个线上项目,既不能将 source map 添加到代码中,又需要调试的情况。就可以通过生成一个 .map 文件,通过添加到浏览器进行调试,如下图所示。
这样就可以在本地调试部署到线上的项目了。
避免在生产中使用 inline-*** 和 eval-***,因为它们会增加 bundle 体积大小,并降低整体性能。
我们在生产环境配置 devtool: 'source-map'
就行了,但是部署的时候不要将map文件也部署上去;或者不配置devtool选项也行,这样就不生成map文件了。
5.4 外部扩展(externals)
externals
配置选项提供了「从输出的 bundle 中排除依赖」的方法。意思就是说在项目中通过 import 引入的依赖在打包的时候不会打包到 bundle
中去,而是通过 script 引入的方式去访问这些依赖。
例如,从 CDN 引入 jQuery,而不是把它打包:
index.html
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
webpack.config.js
module.exports = {
//...
externals: {
jquery: 'jQuery',
},
};
这样就剥离了那些不需要改动的依赖模块,我们在 index.js 中引入 jq 看看打印结果:
import $ from 'jquery';
console.log($('#app'));
5.5 缓存
SplitChunksPlugin
可以用于将模块分离到单独的 bundle
中。webpack 还提供了一个优化功能,可使用 optimization.runtimeChunk
选项将 runtime
代码拆分为一个单独的 chunk
。将其设置为 single
来为所有 chunk
创建一个 runtime bundle
将第三方库(library)(例如 lodash
或 react
)提取到单独的 vendor chunk
文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。
我们在 webpack.config.js 中添加代码
module.exports = {
//...
// https://webpack.docschina.org/guides/caching/
optimization: {
moduleIds: 'deterministic',
// 使用 optimization.runtimeChunk 选项将 runtime 代码拆分为一个单独的 chunk
runtimeChunk: 'single',
splitChunks: {
// 利用 client 的长效缓存机制,命中缓存来消除请求,并减少向 server 获取资源,
// 同时还能保证 client 代码和 server 代码版本一致。 这可以通过
// 使用SplitChunksPlugin 插件的 cacheGroups 选项来实现。
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
}
可以看到,main文件大小从1.78MiB 缩减到 13.2KiB
在加入选项
moduleIds: 'deterministic'
之前重新运行,三个文件的 hash 都变化了。这是因为每个 module.id 会默认地基于解析顺序(resolve order)进行增量。也就是说,当解析顺序发生变化,ID 也会随之改变。因此,简要概括:
main
bundle 会随着自身的新增内容的修改,而发生变化。
vendor
bundle 会随着自身的 module.id 的变化,而发生变化。
manifest
runtime 会因为现在包含一个新模块的引用,而发生变化。
第一个和最后一个都是符合预期的行为,vendor hash
发生变化是我们要修复的。我们将 optimization.moduleIds
设置为 deterministic
。
我们重新运行项目
现在,不论是否添加任何新的本地依赖,对于前后两次构建,
vendor hash
都应该保持一致。
六、生产环境
6.1 配置
在开发环境中,我们需要:强大的 source map
和一个有着 live reloading
(实时重新加载) 或 hot module replacement
(热模块替换) 能力的 localhost server
。
而生产环境目标则转移至其他方面,关注点在于压缩 bundle
、更轻量的 source map
、资源优化等,通过这些优化方式改善加载时间。
由于遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。
在根目录新建build文件夹,在 build
文件夹下新建 webpack.common.js 文件放置通用配置,以及创建生产模式webpack.prod.js 的和开发模式 webpack.dev.js 的配置文件,为了将这些配置合并在一起,我们将使用一个名为 webpack-merge
的工具。
此时 __dirname
的路径已经发生变化啦
npm install --save-dev webpack-merge
webpack.common.js
//webpack.common.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',//入口文件
output: {
filename: '[name].[contenthash].js',// 输出文件
path: path.resolve(__dirname, '../dist'),// 输出文件存放地址
},
resolve: {
extensions: ['.vue', '.js'], //表示在import 文件时文件后缀名可以不写
alias: {
'@': path.join(__dirname, '../src')
//import的文件在src下的时候可以直接写成 @/component/...
}
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: '我是webpack.config配置的标题',
template: './public/index.html',
//压缩HTML
minify: {
removeComments: true, // 移除HTML中的注释
collapseWhitespace: true // 删除空白符与换行符
}
})
],
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
type: 'asset',//在导出一个 data URI 和发送一个单独的文件之间自动选择
},
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader']
}
],
},
// https://webpack.docschina.org/guides/caching/
optimization: {
// deterministic 选项有益于长期缓存
moduleIds: 'deterministic',
// 使用 optimization.runtimeChunk 选项将 runtime 代码拆分为一个单独的 chunk
runtimeChunk: 'single',
splitChunks: {
// 利用 client 的长效缓存机制,命中缓存来消除请求,并减少向 server 获取资源,
// 同时还能保证 client 代码和 server 代码版本一致。 这可以通过
// 使用SplitChunksPlugin 插件的 cacheGroups 选项来实现。
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
};
webpack.dev.js 开发环境:调试定位、热替换等
//webpack.dev.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: '../dist',
hot: true
},
});
webpack.prod.js 生产环境:代码压缩、公共模块分离、资源优化等
//webpack.prod.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool: "source-map",
});
然后我们再把 package.json 中的 script 重新指向新的配置,让 npm run dev
script 中 webpack-dev-server, 使用 webpack.dev.js, 而让 npm run build
script 使用 webpack.prod.js
package.json
//...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack serve --open chrome --config build/webpack.dev.js",
"build": "webpack --config build/webpack.prod.js"
},
//...
6.2 css抽离与压缩
MiniCssExtractPlugin
MiniCssExtractPlugin
插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
npm install --save-dev mini-css-extract-plugin
该插件会将css单独提取到一个样式文件中,并加到html中,所以这里就跟 style-loader
冲突了,我们修改一下配置文件。
//webpack.common.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
//...
plugins: [
new MiniCssExtractniPlugin({
filename: '[name].[contenthash].css'
})
],
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
//...
};
MiniCssExtractPlugin
插件常用于生产环境,再开发环境我们还是可以继续用 style-loader
。
所以我们再修改下配置文件,在 webpack.dev.js 中继续用 style-loader
,而 webpack.prod.js 中用MiniCssExtractPlugin
接下来压缩输出的css文件,安装 css-minimizer-webpack-plugin
插件。
npm i -D css-minimizer-webpack-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
//...
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[id].css',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
optimization: {
minimizer: [
new CssMinimizerPlugin({
parallel: true // 使用多进程并发执行,提升构建速度
}),
],
},
};
我们执行打包命令,可以看到css已经被压缩啦。
这将仅在生产环境开启 CSS 优化。
如果还想在开发环境下启用 CSS 优化,将 optimization.minimize
设置为 true
。
不加上的时候,生成的 main 文件是31.4KiB
我们在webpack.dev.js中加上
//webpack.dev.js
module.exports = merge(common, {
//...
optimization: {
minimize: true
}
})
运行,可以看到,main 文件是13.2KiB
6.3 js压缩
TerserWebpackPlugin
插件使用 terser
来压缩 JavaScript。
如果你使用的是 webpack v5 或以上版本,你不需要安装这个插件。webpack v5 自带最新的 terser-webpack-plugin。如果使用 webpack v4,则必须安装 terser-webpack-plugin v4 的版本。
为调试方便,开发过程中,不压缩代码。 我们加到生成环境中。
// webpack.prod.js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = merge(common, {
// ...
optimization: {
// https://webpack.docschina.org/plugins/terser-webpack-plugin/
// 压缩js
minimize: true
minimizer: [
// https://webpack.docschina.org/plugins/terser-webpack-plugin/
// 压缩js
new TerserPlugin({
parallel: true // 使用多进程并发执行,提升构建速度
})
]
}
})
运行,打包我们的项目,可以看到生成的js文件被压缩成一行。
6.4 图片压缩
哦吼,看到其他博文都有图片压缩的,我也加上。
来吧,跟着官网走一波
需要安装插件
npm install image-minimizer-webpack-plugin --save-dev
对于图片的优化有两种模式
一种是无损模式 一直是有损模式
官网推荐的无损插件有
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev
官网推荐的有损插件有
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev
在 webpack.prod.js 中新增代码
// webpack.prod.js
//...
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
module.exports = {
//...
plugins: [
//...
new ImageMinimizerPlugin({
minimizerOptions: {
// 无损设置
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }],
[
'svgo',
{
plugins: [
{
removeViewBox: false,
},
],
},
],
],
},
})
]
}
打包项目,可以看到 dist 内的图片变小了。
七、集成Vue3
首先,要让 webpack
识别 .vue
文件。就需要 vue-loader
插件,安装插件。
npm install vue@next -S
npm install -D vue-loader@next @vue/compiler-sfc
注意:Vue2.x 时安装的是 vue-template-complier
vue-loader
:解析和转换 .vue
文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的 Loader
去处理。
先来配置让 webpack 去识别.vue文件
//webpack.common.js
const { VueLoaderPlugin } = require('vue-loader/dist/index');
module.exports = {
//...
plugins: [
//...
new VueLoaderPlugin() //解析和转换.vue文件的插件
],
module: [
rules: [
//...
{
test: /\.vue$/,
use: ['vue-loader']
}
]
]
}
在src
目录下新建App.vue
文件试试
<!--App.vue-->
<template>
<div>你好,X1AXX1A</div>
</template>
<script>
import { defineComponent } from "vue"
export default defineComponent({
setup() {
console.log('我是App里的打印')
}
})
</script>
<style scoped>
@import '@/index.css';
</style>
修改index.js文件
//index.js
import App from './App.vue'
import { createApp } from 'vue'
createApp(App).mount('#app')
添加 id 为 app 的 div 到 index.html 中
<!--index.html-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= htmlWebpackPlugin.options.title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="app"></div>
</body>
</html>
接下来,装上Vue全家桶 Vuex
、 vue-router
、 Axios
npm install vuex@next --save
然后在 src 目录下新建 store 文件夹,并在 store 文件夹下新建 index.js 和 modules 文件夹。
modules 文件夹存放各类 store 模块。我们新增一个 user 模块,在 modules 下新建 user.js
// src/store/modules
const state = () => ({
token: null
})
const getters = {
token: (state) => state.token
}
const mutations = {
SET_TOKEN (state, payload) {
state.token = payload
}
}
export default {
namespaced: true,
state,
getters,
mutations
}
然后使用 require.context
动态加载 modules 文件夹下文件。
// src/store/index.js
import { createStore } from 'vuex'
const files = require.context('./modules', false, /\.ts$/)
const modules = {}
files.keys().forEach((key) => {
modules[key.replace(/(\.\/|\.ts)/g, '')] = files(key).default
})
console.log('X1AXX1A modules', modules)
export default createStore({
modules
})
打印看看获取到的 user 模块
随后,在入口文件中引入该文件
// index.js
import App from './App.vue'
import { createApp } from 'vue'
import store from './store'
createApp(App).use(store).mount('#app')
以上,就可以在组件中使用 store 了
我们在App.vue中打印
// App.vue
import { defineComponent } from "vue"
import { useStore } from 'vuex'
export default defineComponent({
setup() {
let store = useStore()
console.log('store', store)
console.log('我是App里123112的打印')
}
})
接下来安装路由器
npm install vue-router@next
在 src下新建 router 文件夹,并在 router 文件夹下新建 index.js,新增如下代码
// src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
import store from '../store'
const routes = [
{
path: '/404',
name: 'NotFound',
component: () => import('@/views/NotFound.vue')
},
{
path: '/',
name: 'Index',
component: () => import(/* webpackChunkName: "about" */ '@/views/Index.vue')
},
{
// 路由配置'*'报错问题,替换成'/:catchAll(.*)'
// caught Error: Catch all routes ("*") must now be defined using a param with a custom regexp
path: '/:catchAll(.*)', // 此处需特别注意置于最底部
redirect: '/404'
}
]
const router = createRouter({
history: createWebHashHistory(), // hash模式:createWebHashHistory,history模式:createWebHistory
routes
})
export default router
在入口文件中引入该文件
// index.js
import App from './App.vue'
import { createApp } from 'vue'
import store from './store'
import router from './router'
createApp(App).use(store).use(router).mount('#app')
修改App.vue
// App.vue
<template>
<router-view />
</template>
<script>
import { defineComponent } from "vue"
export default defineComponent({})
</script>
在 views 中新建Index.vue,在 Index.vue 中新增以下代码中,打印看看
// src/views/Index.vue
<template>
<div>我是首页</div>
</template>
<script>
import { defineComponent } from "vue"
// import { useStore } from 'vuex'
import { useRouter, useRoute } from 'vue-router'
export default defineComponent({
setup() {
// let store = useStore()
let router = useRouter()
let route = useRoute()
// console.log('store', store)
console.log('router', router)
console.log('route', route)
console.log('我是App里123112的打印')
}
})
</script>
接下来就是 axios
npm i axios
按我们的习惯封装axios,做请求拦截等。
import axios from 'axios'
//添加请求拦截器
axios.interceptors.request.use(function(config){
//在发送请求之前做某事
return config;
},function(error){
//请求错误时做些事
return Promise.reject(error);
});
//添加响应拦截器
axios.interceptors.response.use((response)=>{
//对响应数据做些事
return response;
},(error)=>{
return Promise.reject(error);
});
export default axios
以上的准备做好之后,就可以开始我们的 Vue 3
项目之旅啦。
最后,谢谢你读完本篇文章,希望对你能有所帮助,如有问题欢迎各位指正。
转载自:https://juejin.cn/post/6942076401986043918