前言:由于项目要进行性能优化,项目有点老:Vue2+Webpack3。正好借此机会系统的学习一下Webpack。以下是我总结的Webpack的一些内容(如有不对,欢迎指正)。这篇文章还未涉及到CSS代码分割、Tree shaking等,因为我也没想好怎么弄😂。下篇文章我会记录使用Webpack对项目进行的一些代码优化。
Webpack的由来
在传统的页面设计中,当随着业务的增多,我们的脚本文件也越来越多,然后按照特定的顺序把脚本依次引入到页面中。这种方法存在很多问题:全局命名变量污染、脚本请求过多导致页面加载时间过长。
Webpack是什么
Webpack
是Javascript应用程序的静态模块打包器,它允许我们将多个脚本文件打包成少数几个可以通过浏览器加载的文件。
使用Webpack进行打包
命令:
npx webpack <文件名>
- npx :
- Node.js的包执行工具,它随Node.js的包管理器npm一起安装,npx允许你执行在本地或远程npm包中的命令,而不需要全局安装这些包。这意味着即使你没有全局安装Webpack,也可以使用npx来运行Webpack。
- webpack :
- 指的是
webpack
本身,一个静态模块打包工具。你在项目中运行npx webpack
时,Webpack会根据你的配置(通常在一个名为webpack.config.js
的文件中)来打包你的JavaScript应用。
- 指的是
补充:也可指定一个Webpack配置文件进行打包 npx webpack --config <文件名>
,显然npx 这个命令不常见,我们经常使用 npm run build
那是因为 npm run
命令用于执行在Node.js项目的 package.json
文件中定义的脚本。如:
json
{
"name": "1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10"
}
}
我们执行 npm run build
相当于执行scripts
脚本中的build
, Node.js 会帮我们从node_module
文件夹下的.bin
文件夹下找到webpack
,然后执行
Webpack基本配置项
Webpack默认只支持
CommonJs
规范,需使用require
进行模块引入
entry
Webpack打包时文件的入口
js
module.exports = {
entry: "./index.js"
}
output
Webpack在哪里输出打包后的文件。该字段是一个对象,通常包含以下关键属性:
filename
:定义输出文件的名称path
:指定输出文件的绝对路径,通常结合Node.js的path模块结合使用publicPath
:一个非常重要的配置项,它指定了输出文件的公共URL。当我们打包生产环境代码时。它用于在文件前加入CDN链接来确保资源的正常加载
js
const path = require ("path") // 引入Node中的path模块
module.exports = {
entry: "./index.js" // 表示以webpack当前文件夹下的index.js文件作为入口
output: {
// 文件打包完之后的文件名称
filename: "bundle.js",
// __dirname是当前目录下, bundle生成文件夹名称
path: path.resolve(__dirname, "bundle"),
publicPath: "http://cdn.com.cn/"
}
}
打包后index.html:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
// 在bundle.js前加入了 http://cdn.com.cn/
<script src="http://cdn.com.cn/bundle.js"></script></body>
</html>
补充:上述案例都是一个入口Js文件,如果在页面中引用多个Js文件怎么办?
答:可以将entry写成一个对象,提供多个文件
jsconst path = require ("path") module.exports = { entry: { // main -> 打包后文件名 "./index.js" -> 指定打包路径 main: "./index.js", test: "./test.js" }, output: { filename: "[name].js", path: path.resolve(__dirname, "dist") } }
上述案例中 main 、test 为我们想要打包后生成的文件名,需要结合
[name]
占位符进行使用
module
配置项是webpack用于定义如何处理项目中不同类型的模块。以下是它的一些关键属性
- rules :module对象中最重要的一个属性,用于定义一系列的加载器(loaders)。加载器可以转换文件,或者对文件进行编译处理。如:ES6转ES5,TypeScript转JavaScript,Sass转CSS 。每条规则包含以下属性:
- test:一个正则表达式,用于匹配需要应用这个规则的文件。
- use :是rules配置中核心部分,它用于指定模块的加载器(loaders),通常是一个包。以下是它的一些关键属性:
- loader:文件加载器,指定相应loader进行处理
- options :设置文件的命名规则,输出路径。它也存在几个关键属性
- name :指定输出文件的名称,可使用占位符来定义文件的格式: [name] 、[ext] 、[hash] , 分别表示原始文件名称、扩展名、将文件名转换为hash值
- outputPath:指定输出文件所在的文件夹名称
- exclude :排除某些文件,通常用于排除 node_modules 目录。
- include:和exclude相反,用于指定包含的文件
plugins
用于指定一系列的插件,用于扩展和增强Webpack的功能。它是一个数组,数组中的每一个元素都是一个插件实例。 常见的Webpack插件:
HtmlWebpackPlugin(生成html文件) 、CleanWebpackPlugin(清除原有的打包的文件) 、HotModuleReplacementPlugin(热模块)
js
const HtmlWebpackPlugin = require ("html-webpack-plugin")
const { CleanWebpackPlugin } = require ("clean-webpack-plugin")
const webpack = require ("webpack")
module.exports = {
plugins: [
new HtmlWebpackPlugin ({
template: "./index.html" // 这里用于指定一个Html文件
}),
new CleanWebpackPlugin(), // 删除掉原本存在的打包文件
new webpack.HotModuleReplacementPlugin()
]
}
sourceMap
sourceMap
是一种在已编译的代码中提供原始源代码信息的技术。它使得开发者能够在调试已压缩、合并或转换(TypeScript -> JavaScript)的代码时,追踪到原始源代码。
- 配置
sourceMap
js
module.exports = {
// 开发模式建议配置 cheap-module-eval-source-map
mode: "development"
devtool: "cheap-module-eval-source-map"
// 生产打包建议配置:cheap-module-source-map
mode: "production",
devtool: "cheap-module-source-map"
}
WebpackDevServer
webpack-dev-server
包为我们提供了一个简单的web服务器和实时重新加载的功能。
- 特性:
- 支持热模块替换(HMR):HTM允许模块在运行时被更新,而无需完全刷新页面
- 提供 HTTP 服务:它为您的应用程序提供了一个简单的 HTTP 服务器,这对于开发 SPA(单页应用程序)特别有用。
- 代理API请求:可自己进行配置
- 它的一些主要配置项
contentBase
指定服务器从哪个目录提供静态资源。这些资源不会被webpack处理。如:使用
VUE CLI
创建的项目,public
或static
会存放静态资源 --> 静态的html文件,大型的图片等
js
module.exports = {
devServer: {
contentBase: "./public"
}
}
port
指定要监听的端口
js
module.exports = {
devServer: {
port: 8080
}
}
hot
是否启用HMR(热模块替换),不刷新整个页面的情况下通过新模块替换旧模块, 通常结合HotModuleReplacementPlugin使用
js
const webpack = require ("webpack")
module.exports = {
devServer: {
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
}
open
启动服务器后自动打开浏览器
js
module.exports = {
devServer: {
open: true
}
}
proxy
代理一些特定的URL到另一台服务器上。用于前后端分离时请求后端的API,以下是它的一些关键属性:
target
指定要代理到的目标服务器地址
js
module.exports = {
devServer: {
proxy: {
// 以/api为开头的地址代理到 http://localhost:3000
"^/api": {
target: "http://localhost:3000"
}
}
}
}
pathRewrite
重写请求路径。 它的使用场景:
前端和后端分离 :前端开发时,可能会有一个统一的 API 前缀,如/api
,而在后端服务器上,实际的路径可能不包含这个前缀。
开发环境代理:为了方便开发,需要一个代理来转发请求到后端 API 服务器,同时去掉不必要的路径前缀。
js
module.exports = {
devServer: {
proxy: {
"/api": {
target: "http://localhost:3000",
pathWrite: {"^/api": ""}
}
}
}
}
上述案例:请求路径中的/api
部分会被删除,如/api/demo
会被代理到http:localhost:3000/demo
changeOrigin
通常设为 true
js
module.exports = {
devServer: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true
}
}
}
secure
若代理目标是https服务器,通常设置为false
js
module.exports = {
devServer: {
proxy: {
"/api": {
target: "https://localhost:3000",
secure: false
}
}
}
}
context
指定特定的路径被代理
js
module.exports = {
devServer: {
proxy: [{
context: ["/auth", "/api"],
target: "http://localhost:3000"
}]
}
}
上述案例:指定以auth 和api为开头的URL路径被代理
处理图片模块
webpack默认只能处理JS模块部分,无法处理图片。如果想要解析图片,需要用到两个包
url-loader
和file-loader
,并在webpack
中module
配置项中进行配置
url-loader
将图片解析为base64位编码。应用于占据内存较小的图片
file-loader
将图片单独声明一个文件,在页面上进行引用。 应用于较大图片
我们再使用这两个loader中,肯定是希望较小的图片解析为base64位编码,直接在页面中进行引用。图片过大则使用flie-loader
单独解析。那如何进行区分呢?答:在options
配置项中使用limit
进行限制,通常是20480(20kb)
字节
js
const path = require ("path")
module.exports = {
module: {
rules: [
{
test: /.(png|jpe?g|gif)$/,
use: {
loader: "url-loader",
options: {
name: "[name].[ext]", // 输出原始文件名和扩展名
outputPath: "images", // 输出文件所在目录
limit: 20480 // 小于20480使用url-loader进行解析,大于则使用file-loader进行解析
}
}
}
]
}
}
处理样式模块
Webpack默认只能处理Js模块,无法处理引用的Css文件。需要使用处理Css模块的loader:style-loader、css-loader
css-loader
用于处理Css文件,例如:我们在JS文件中使用import引入Css文件时,css-loader会处理这个Css文件
style-loader
将处理过的Css文件注入到页面中的Style标签中
js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"] // 执行顺序,从右往左,从下往上
}
]
}
}
当我们想使用sass语法时也需要使用相应的加载器(loader)来将sass与语法解析成css
loader:
sass-loader
node-sass
sass-loader
和css-loader类似,用来处理引入的.scss/.sass文件
node-sass
它是一个库,将.sass/.scss文件转换为有效的Css
js
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader"]
}
]
}
}
此外当我们使用Css3样式时 如:transform属性时,某些浏览器版本可能过低造成不兼容或需要兼容不同类型浏览器时,我们需要使用
postcss-loader
和autoprefix
对css进行转换处理。
postcss-loader
它主要允许我们在使用webpack构建项目时使用到
postCss
postCss
的作用:通过一系列插件来拓展和增强Css的功能。这些插件可以有:
- 添加浏览器前缀:通过
autoprefix
插件来自动添加不同浏览器所需的CSS前缀。- 优化Css:压缩CSS代码,删除不必要的代码和注释,提高性能。如
purgecss
使用postcss-loader
:需要分别在 webpack.config.js
、postcss.config.js
、package.json
三个文件夹中进行修改
webpack.config.js
基本配置
js
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"]
}
]
}
}
postcss.config.js
文件配置(若无需手动创建)
js
module.exports = {
plugins: [
require ("autoprefixer") // 引入 autoprefixer插件
]
}
package.json
文件:添加 browserslist
字段 -> 指定项目支持浏览器的范围
json
"browserslist": [
"> 1%",
"last 2 versions"
]
Css
模块化
当我们在
main.js
(项目文件的入口)文件中引入一个.css文件,它的样式默认对全局的的标签生效,那么如何只对一个指定的模块化生效呢?答:css-loader
进行模块配置,将modules 字段设置为true
webpack.config.js
:
js
module.exports = {
module: {
rules: [
{
test: /\.scss$/
use: [
"style-loader",
{
loader: "css-loader",
options: {
modules: true
}
},
"postcss-loader",
"sass-loader",
]
}
]
}
}
main.js
示例:
js
import jspp from "./images/1.jpeg" // 引用本地图片,当作src
import styles from "./index.scss" // 引入scss
const img = new Image()
img.src = jspp
img.className += `${styles.logo}` // Css模块化
const app = document.getElementById("app")
app.appendChild(img)
处理字体样式
和处理图片类似,需要使用 url-loader和file-loader
webpack.config.js
js
module.exports = {
module: {
rules: [
{
test: /\.(eot|svg|ttf|woff)$/,
loader: "url-loader",
options: {
limit: 20480
}
}
]
}
}
上述案例:我们使用url-loader
解析字体文件,小于20480B使用url-loader解析成base64直接渲染到页面上,大于则使用了file-loader
单独解析引用
ES6 -> ES5
前言:因为Babel
的版本不同,版本的配置也不同。以下讲解的是Babel@7
版本以上的配置。
当我们使用ES6语法(如: 箭头函数、let、const等)时,一些老版本浏览器兼容不了这些API,就需要将ES6转成ES5。所需依赖:babel-loader 、@babel/core 、@babel/p引擎set-env 。但同时ES6新增的内置对象(如:Promise、async/await)和方法(如:map)低版本的JavaScript引擎根本不存在,也需要进行解决。所需依赖:core-js 、regenerator-runtime
babel-loader
它相当于一个中间件,将Babel和Webpack连接起来。在Webpack编译中,它将
.js
文件传递给Babel进行编译
@babel/core
Babel的核心功能包,它包含了Babel的转换引擎和核心API。其本身不参与代码转换,而是作为其他Babel工具(插件和预设)的基础 它的编译过程如下:
babel-loader
调用@babel/core
的API,而@babel/core
又会根据配置的插件和预设来转换代码
@babel/preset-env
Babel的预设,ES6转换为向后兼容的JavaScript版本
core-js
一个标准的polyfill ,它包含ES6新增的一些(如:Promise、Map、Set、Symbol等),将这些特性运行在低版本浏览器中。和preset-env搭配使用,可以按需进行引入
regenerator-runtime
配合core-js 一起使用,专门用于转换生成器(Generators)和异步函数(
async
/await
)
进行配置
webpack.config.js
js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
loader: "babel-loader",
options: {
presets: [[
"@babel/preset-env", {
useBuiltIns: "usage",
corejs: 3 // 这里需要和core-js包版本一致
}
]]
},
exclude: /node_modules/
}
]
}
}
当然你如果不想在webpack.config.js 中配置太多babel 属性,我们也可以创建一个.babelrc
文件,Webpack在编译时会自动加载这个文件 如:
webpack.config.js
js
module.exports = {
module: {
rules: [
{
test: /.js$/,
loader: "babel-loader",
exclude: /node_modules/
}
]
}
}
.babelrc
js
{
"presets": [[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]]
}
以下是测试的Babel版本(无报错)
json
"dependencies": {
"core-js": "^3.33.2",
"regenerator-runtime": "^0.14.0",
}
"devDependencies": {
"babel-loader": "^8.2.2",
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11",
}
打包Vue代码
所需依赖vue-loader ,vue-template-compiler
- vue2 版本通常对应 vue-loader@15版本
- vue3 版本通常对应 vue-loader@16版本
- vue-template-compiler ,它的版本与vue的版本相对应
vue-loader
.vue
文件是一个单文件组件,它将HTML 、CSS 、JS 封装到了一个文件夹内。vue-loader 的作用就是将 HTML 、CSS 、JS 提取出来并将它们转换成JavaScript模块形式
vue-template-compiler
主要是来处理
Vue
文件中HTML
部分。当我们使用vue-loader 时,vue-loader 内部会使用vue-template-compiler 来处理HTML
模版编译
Vue2版本配置
package.json
json
"dependencies": {
"vue": "2.6.11"
}
"devDependencies": {
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.11",
}
webpack.config.js
js
const VueLoaderPlugin = require ("vue-loader/lib/plugin")
module.exports = {
module: {
rules: [
{
test: /.vue$/,
loader: "vue-loader"
},
{
test: /\.js$/,
loader: "babel-loader",
exclude: /node_modules/,
},
{
test: /\.css$/,
// loader执行顺序:从后往前,从下往上。
// style-loader 解析样式 css-loader 解析css引入之间的关系
use: [
"vue-style-loader",
"css-loader",
"postcss-loader"
]
},
]
},
plugins: [
new VueLoaderPlugin()
]
}
注意: 一定要使用 VueLoaderPlugin 插件。它可以将你的rules 配置项应用到Vue单文件组件内
vue-loader中文文档