webpack5 基础打包
一、开始
1. webpack简介
本笔记中Webpack版本为Webpack5。
1.1 官方的解释
webpack is a static module bundler for modern JavaScript applications
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)
官网图:
webpack在处理应用程序时,它会根据命令或者配置文件找到入口文件,从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js文件、css文件、图片、字 体等),然后遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析)。
- 创建Vue项目、React项目时,都会使用到脚手架(cli),他们都是基于Webpack来帮助我们构建模块化、less、TypeScripts、打包优化。
- webpack官网:https://webpack.js.org/
- webpack中文文档官网:https://www.webpackjs.com/
1.2 webpack 使用前提
为电脑安装 NodeJS
环境,Node官网。
2. webpack的安装
安装:webpack
、webpack-cli
# 全局安装-----------意味着在自己电脑上本地安装
npm i webpack webpack-cli -g
# 局部安装(开发依赖)------- 在某个项目上安装
npm i webpack webpack-cli -D
两者间的关系:
- 在项目根路径下执行 webpack 命令,会执行node_modules下的 .bin 目录下的webpack;
- webpack在执行时是依赖webpack-cli的,如果没有安装就会报错;
- 而webpack-cli中代码执行时,才是真正利用webpack进行编译和打包的过程;
- 所以在安装webpack时,我们需要同时安装webpack-cli(第三方的脚手架事实上是没有使用webpack-cli的,而是类似于自己的vue-service-cli的东西)
3.webpack的默认打包
前提:局部的webpack演示项目
npm init
创建package.json 管理当前项目的信息及依赖等npm i webpack webpack-cli -D
在当前文件夹(项目)局部安装webpack
当在项目目录下执行webpack
或npx webpack
命令时,
# 直接执行 webpack,会去寻找全局的webpack依赖来打包,如果全局未安装则会报错
# webpack
# npx webpack,利用当前node_modules中的依赖进行webpack打包
npx webpack
可在package.json中创建scripts脚本:
"scripts": {
// 设置之后,可直接使用 npm run build即可运行该脚本,等同于 npx webpack。
// 为何build的值不是npx webpack却等同于npx webpack的效果,是因为通过运行package.json中的scripts脚本,默认使用当前node_modules中的依赖
"build": "webpack"
},
3.1 webpack命令后的打包过程
- 入口问题:webpack会查找当前目录下的 src/index.js 作为入口,从而开始收集项目运行所需的所有依赖,然后进行打包。如果当前项目中没有存在src/index.js文件,那么会报错;
- 出口问题:打包完成后,会在目录下生成一个dist文件夹,里面存放一个main.js的文件或其他媒体依赖(img/font等),就是我们打包之后的文件。
配置来指定入口和出口:
npx webpack --entry ./src/main.js --output-path ./build
4. Webpack的配置文件
在通常情况下,webpack需要打包的项目是非常复杂的,并且我们需要一系列的配置来满足要求,默认配置必然是不行的,需要一个专门的文件来进行打包配置,即webpack.config.js
。
在根目录下创建一个webpack.config.js
文件,来作为webpack的配置文件:
const path = require("path");
// 导出配置信息
module.exports = {
// 配置入口信息
entry: "./src/main.js",
// 出口信息
output: {
path: path.resolve(__dirname, "dist")
}
}
// 以上配置等同于默认打包
// npm run build 进行打包和默认打包效果一致
二、基本案例-loader
1. css-loader
1.1 目录结构
├── dist (打包出口文件夹)
├── mode_modules
├── src
| ├── css
| | ├── style.css
| ├── js
| | ├── element.js
| ├── main.js
├── index.html
├── webpack.config.js
1.2 代码
-
style.css
.title{ /* color: red; */ /* 最新CSS颜色,后面两位表示透明度 */ color: #12345678; font-size: 30px; font-weight: 600; user-select: none; }
-
element.js
import "../css/style.css"; const title = document.createElement("div"); title.className = "title"; title.innerHTML = '你好啊,李大钊' document.body.append(title);
-
main.js
import './js/element';
-
index.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> <script src="./dist/main.js"></script> </body> </html>
1.3 编译
运行:
npm run build
运行结果:报错,原因是./src/css/style.css
模块解析失败,需要一个合适的 loader 来处理这种文件。Webpack默认可以解析处理JS文件,但不能直接处理css文件。
1.4 loader是什么
loader 是可以用于对模块的源代码进行转换;
可以将css文件也看成是一个模块,我们是通过import来加载这个模块的;
在加载这个模块时,webpack其实并不知道如何对其进行加载,我们必须制定对应的loader来完成这个功能。
1.5 css-loader
加载css文件,就需要处理css文件的loader。常用的是:css-loader
。
# 安装css-loader
npm install css-loader -D
1.5.1 配置信息
通过在webpack.config.js
中配置,在module.rules中。
module
为对象,rules
为对象数组,其中每一个对象可设置属性- test属性:用于对 resource(资源)进行匹配的,通常会设置成正则表达式
- use属性:为数组,数组每一项为对象或字符串
- 数组项为对象时:
- loader::必须有一个loader属性,对应的值是一个字符串;
- options:可选的属性,值是一个字符串或者对象,值会被传入到loader中
- 字符串时,如:
use: ['style-loader']
是 loader 属性的简写方式(如:use: [{loader:'style-loader'}]
;
- 数组项为对象时:
- loader属性:
use: [{loader:'style-loader'}]
的简写
1.5.2 配置代码
webpack.config.js
const path = require('path');
module.exports = {
entry: "./src/main.js",
output: {
// 输出出口路径
path: path.resolve(__dirname, "./build"),
// 出口JS文件名
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.css$/, //正则表达式
// loader的写法(语法糖)
// loader: "css-loader" // 写法1
// use: ["css-loader"] // 写法2
// 写法3
use: [
{ loader: "css-loader" }
]
}
]
}
}
通过
css-loader
来加载css文件了,但这个css在我们的代码中并没有生效(页面没有效果),这是因为css-loader只是负责将.css文件进行解析,并不会将解析之后的css插入到页面中。如果我们希望再完成插入样式style的操作,那么我们还需要另外一个loader,就是style-loader。
2. style-loader
2.1 安装
npm install style-loader -D
2.2 配置 style-loader
-
在配置文件中,添加style-loader
-
注意:因为loader的执行顺序是从右向左(或者说从下到上,或者说从后到前的),所以我们需要将style-loader写到css-loader的前面;
module: { rules: [ { test: /\.css$/, //正则表达式 //use: [ // "style-loader", // "css-loader", //], use: [ { loader: "style-loader" }, { loader: "css-loader" } ] }
-
重新执行编译
npm run build
,可以发现打包后的css已经生效了。
问题:
开发中,我们可能会使用less、sass等预处理器来编写css样式,那 Webpack如何解析这些文件呢?
首先我们需要确定:less、sass等编写的css需要通过工具转换成普通的css,然后css被浏览器识别,从而得到相应的样式。
3. less-loader
处理 less预处理器的语言。
3.1 安装
npm install less-loader -D
3.2 配置
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
"less-loader"
]
},
执行npm run build
,less就会被转换成css,然后css被解析,随后style样式生效。
这个工具可以帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置
4. 加载图片
4.1 目录结构
├── dist (打包出口文件夹)
├── mode_modules
├── src
| ├── css
| | ├── img.css
| ├── img
| | ├── bilibili-1.png
| | ├── wlop.jpg
| ├── js
| | ├── element.js
| ├── main.js
├── index.html
├── webpack.config.js
4.2 代码
-
img.css
.bg-div { background-image: url('../img/wlop.jpg'); width: 200px; height: 200px; background-size: contain; }
-
element.js
import "../css/img.css" // 图片src要正确引入,需要将所需图片当模块引入 import bilibiliImg from '../img/bilibili-1.png' // 背景图片 div const bgDivElement = document.createElement("div"); bgDivElement.className = "bg-div"; // 图片src const imgEve = document.createElement('img'); /** * 直接赋值在HTML中不能正确渲染图片,因为写死的相对路径下找不到对应的图片 */ // imgEve.src = "../img/bilibili-1.png"; imgEve.src = bilibiliImg; document.body.append(bgDivElement); document.body.append(imgEve);
-
其他文件(index.html、main.js),同上
💡🔥 webpack4,使用
file-loader
或url-loader
进行处理图片,而Webpack5则不使用这些,使用资源模块类型
4.3 file-loader
要处理jpg、png等格式的图片,我们也需要有对应的loader:file-loader,file-loader的作用就是帮助我们处理import/require()方式引入的一个文件资源,并且会将它放到我们输出的文件夹中。
4.3.1 安装
npm install file-loader -D
4.3.2 配置
文件的命名规则:
- 有时候我们处理后的文件名称按照一定的规则进行显示:
- 比如保留原来的文件名、扩展名,同时为了防止重复,包含一个hash值等;
- 我们可以使用
PlaceHolders
来完成,webpack给我们提供了大量的PlaceHolders来显示不同的内容
- https://webpack.js.org/loaders/file-loader/#placeholders
- 们可以在文档中查阅自己需要的placeholder
- 介绍几个最常用的placeholder:
- [ext]: 处理文件的扩展名;
- [name]:处理文件的名称;
- [hash]:文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制);
- [contentHash]:在file-loader中和[hash]结果是一致的(在webpack的一些其他地方不一样,后面会讲到);
- [hash:]:截取hash的长度,默认32个字符太长了;
- [path]:文件相对于webpack配置文件的路径;
{
test: /\.(jpe?g|png|gif|svg)$/,
use: {
loader: "file-loader",
options: {
// 设置文件存储的位置
// outputPath: "img"
// 等同于以下
name: "img/[name]-[hash:6][ext]"
}
}
},
4.4 url-loader
url-loader和file-loader的工作方式是相似的,但是可以将较小的文件,转成base64的URI。
4.4.1 安装
npm install url-loader -D
4.4.2 配置
npm run build
后,在dist文件夹中,我们会看不到图片文件,默认情况下url-loader会将所有的图片文件转成base64编码。
但是开发中我们往往是小的图片需要转换,但是大的图片直接使用图片即可,这是因为小的图片转换base64之后可以和页面一起被请求,减少不必要的请求过程,而大的图片也进行转换,反而会影响页面的请求速度。
通过limit
属性进行限制哪些大小的图片转换和不转换
{
test: /\.(jpe?g|png|gif|svg)$/,
use: {
loader: "url-loader",
options: {
// 设置文件存储的位置
outputPath: "img",
name: "[name]-[hash:6][ext]",
// 小于100kb的图片进行base64编码
limit: 100 * 1024
}
}
}
4.5 资源模块类型(asset module type)
在webpack5之前,加载这些资源我们需要使用一些loader,比如raw-loader 、url-loader、file-loader;
在webpack5开始,我们可以直接使用资源模块类型(asset module type)
,来替代上面的这些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,并且配置资源体 积限制实现;
4.5.1 配置
举例使用 asset
const path = require('path');
module.exports = {
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, "./build"),
filename: "bundle.js",
// 通过asset打包的文件名
// assetModuleFilename: "img/[name]-[hash:6][ext]"
},
module: {
rules: [
{
test: /\.(jpe?g|png|gif)$/,
type: "asset",
//解析
parser: {
//转base64的条件
dataUrlCondition: {
maxSize: 100 * 1024, // 约定大小
}
},
generator: {
// outputPath: 'img'
// 与outputPath是相同的,这个写法引入的时候也会添加好这个路径
// 与在output出口配置assetModuleFilename属性一致 ↑↑↑↑
filename: 'img/[name]-[hash:6][ext]',
},
}
]
}
}
5. 加载字体文件
字体资源可从阿里巴巴矢量库中下载。前提:Webpack5
5.1 目录结构
├── dist (打包出口文件夹)
├── mode_modules
├── src
| ├── font
| | ├── iconfont.css
| | ├── iconfont.eot
| | ├── iconfont.ttf
| | ├── iconfont.woff
| | ├── iconfont.woff2
| ├── js
| | ├── element.js
| ├── main.js
├── index.html
├── webpack.config.js
5.2 代码
-
element.js
// 引入字体css import "../font/iconfont.css"; // font字体的使用 const iEl = document.createElement("i"); iEl.className = "iconfont icon-caps-lock"; document.body.append(iEl);
-
webpack.config.js
{ test: /\.(eot|ttf|woff2?)$/, // webpack4 // use: { // loader: "file-loader", // options: { // name: "font/[name]-[hash:6].[ext]" // } // } // webpack5 type: "asset/resource", generator: { filename: "font/[name]-[hash:6][ext]" } }
-
其他文件同上
三、插件Plugin
1.认识Plugin
Webpack官方描述:
While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables.
Loader用于转换某些类型的模块,Plugin 可以用来执行更广泛的任务,如打包优化、资源管理和环境变量注入。
2. CleanWebpackPlugin
前面的案例中,每npm run build
打包前,都需要手动删除之前打包的文件夹(如:默认的dist文件夹)。
我们可以利用一个插件来帮我们自动删除----CleanWebpackPlugin
2.1 安装
npm i clean-webpack-plugin -D
2.2 使用
-
webpack.config.js
const path = require("path"); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "build"), // 设定文件夹名和打包的JS文件名 filename: "js/build.js" }, module: { rules: [ //{} 匹配规则对象 ] }, plugins: [ // 通过new一个新的实例 new CleanWebpackPlugin() ] }
3.HtmlWebpackPlugin
在之前的案例中,我们的index.html
文件在项目根目录下,而最终打包的dist
文件夹下则没有index.html
文件,当进行项目部署时,必然是需要有对应的入口文件index.html
,所以我们需要对index.html
进行打包处理。
3.1 安装
npm i html-webpack-plugin -D
3.2 使用
使用 HtmlWebpackPlugin 插件可不在项目根目录下新建
index.html
文件,插件可根据项目依赖自动创建对应的index.html
文件在打包文件夹中。
-
webpack.config.js
const path = require("path"); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "build"), // 设定文件夹名和打包的JS文件名 filename: "js/build.js" }, module: { rules: [ //{} 匹配规则对象 ] }, plugins: [ // 通过new一个新的实例 new CleanWebpackPlugin(), new HtmlWebpackPlugin() ] }
-
新生成的
index.html
文件(压缩过后的,js引用的为js文件夹下的build.js)<!doctype html><html><head><meta charset="utf-8"><title>Webpack App</title><meta name="viewport" content="width=device-width,initial-scale=1"><script defer="defer" src="js/build.js"></script></head><body></body></html>
这个文件是如何生成的呢?
- 默认情况下是根据
ejs
的一个模板来生成的; - 在html-webpack-plugin的源码中,有一个default_index.ejs模块;
- 默认情况下是根据
3.3 自定义HTML模板
如果我们想在自己的模块中加入一些比较特别的内容:
- 比如添加一个
noscript
标签,在用户的JavaScript被关闭时,给予响应的提示; - 比如在
开发vue
或者react
项目时,我们需要一个可以挂载后续组件的根标签;
这时就需要一个属于自己的index.html
模版
-
目录结构
├── dist (打包出口文件夹) ├── mode_modules ├── public | ├── index.html | ├── favico.hico ├── src | ├── main.js ├── webpack.config.js
-
index.html
模板---例子如下:<!DOCTYPE html> <html lang=""> <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"> <!-- 在webpack中的 DefinePlugin 中配置变量 --> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <!-- 在webpack中的plugin中配置title变量 --> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>
上面的代码中,会有一些类似这样的语法
<% 变量 %>
,这个是EJS
模块填充数据的方式。 -
webpack.config.js
// DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装): // HTML模板中 BASE_URL 变量的设置 const { DefinePlugin } = require("webpack"); plugins: [ new HtmlWebpackPlugin({ // 指定HTML模板的位置 template: "./public/index.html", // HTML模板中 htmlWebpackPlugin.options.title 对应的变量 title: "哈哈哈哈" }), // 设置变量 new DefinePlugin({ BASE_URL: "'./'" }), ]
4.CopyWebpackPlugin
在vue的打包过程中,如果我们将一些文件放到public的目录下,那么这个目录会被复制到dist文件夹中。
这个复制的功能,我们可以使用CopyWebpackPlugin
来完成;
4.1 安装
npm install copy-webpack-plugin -D
4.2 配置
- 复制的规则在patterns中设置:
- from:设置从哪一个源中开始复制;
- to:复制到的位置,可以省略,会默认复制到打包的目录下;
- globOptions:设置一些额外的选项,其中可以编写需要忽略的文件:
- .DS_Store:mac目录下回自动生成的一个文件;
- ndex.html:也不需要复制,因为我们已经通过HtmlWebpackPlugin完成了index.html的生成;
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: "public",
to: "./",
globOptions: {
ignore: [
"**/index.html"
]
}
}
]
})
]
四、其他配置或工具
1. 调试源码配置
默认情况下,我们写完代码然后将其打包,当编写的代码有错误时,在浏览器控制台上查找错误,将会指到打包后的JS文件上,而不知道具体哪个源文件出错。
因此得加以配置,更好的调试项目。
module.exports = {
// 设置模式
// development 开发阶段使用
// production 准备打包上线时使用
mode: "development",
// 建立js映射文件,方便调试代码和报错
devtool: "source-map"
}
2.PostCSS工具
2.1 PostCSS是什么
- PostCSS是一个通过JavaScript来转换样式的工具;
- 这个工具可以帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置;
- 但是实现这些功能,我们需要借助于PostCSS对应的插件;
2.2 使用PostCSS
- 查找PostCSS在构建工具中的扩展,比如webpack中的postcss-loader;
- 选择可以添加你需要的PostCSS相关的插件;
2.3 命令行使用postcss
不借助webpack也能在终端使用,需要安装工具postcss-cli。
npm install postcss postcss-cli -D
2.3.1 自动实现添加前缀的CSS
- https://autoprefixer.github.io/
- 我们可以在上面的网站中查询一些添加css属性的样式;
初始css样式:
.content {
user-select: none;
}
**使用autoprefixer插件和postcss工具: **
# 安装autoprefixer插件
npm install autoprefixer -D
# 直接使用使用postcss工具,并且制定使用autoprefixer
npx postcss --use autoprefixer -o end.css ./src/css/style.css
**转化后的css如下: **
.content {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
2.4 postcss-loader
真实开发中我们必然不会直接使用命令行工具来对css进行处理,而是可以借助于构建工具。在webpack中使用postcss就是使用postcss-loader来处理的;
安装:
npm install postcss-loader -D
使用:
我们修改加载css的loader,因为postcss需要有对应的插件才会起效果,所以我们需要配置它的plugin。(需要下载插件)
module: {
rules: [
{
test: /\.css$/, //正则表达式
use: [
"style-loader",
"css-loader",
// "postcss-loader"
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
require("autoprefixer")
]
}
}
}
]
}
]
}
2.5 单独的postcss配置文件
我们也可以将这些配置信息放到一个单独的文件中进行管理:
-
将webpack.config.js中的
postcss-loader
的配置信息删掉,只指定使用postcss-loader
module: { rules: [ { test: /\.css$/, //正则表达式 use: [ "style-loader", "css-loader", "postcss-loader" ] } ] }
-
在根目录下创建
postcss.config.js
-
module.exports = { plugins: [ require("autoprefixer") ] }
2.6 postcss-preset-env
事实上,在配置postcss-loader
时,我们配置插件并不需要使用autoprefixer
。
我们可以使用另外一个插件:postcss-preset-env
- postcss-preset-env也是一个postcss的插件;
- 它可以帮助我们将一些现代的CSS特性,转成大多数浏览器认识的CSS,并且会根据目标浏览器或者运行时环境 添加所需的polyfill;
- 也包括会自动帮助我们添加autoprefixer(所以相当于已经内置了autoprefixer);
安装:
npm install postcss-preset-env -D
使用:
-
在
postcss.config.js
文件中修改掉之前的autoprefixer即可module.exports = { plugins: [ require("postcss-preset-env") ] }
五、Babel
1. 认识Babel
事实上,在开发中我们很少直接去接触babel,但是babel对于前端开发来说,目前是不可缺少的一部分:
- 开发中,我们想要使用ES6+的语法,想要使用TypeScript,开发React项目,它们都是离不开Babel的;
- 所以,学习Babel对于我们理解代码从编写到线上的转变过程至关重要;
1.1 Babel是什么
Babel是一个工具链,主要用于旧浏览器或者环境中将ECMAScript 2015+代码转换为向后兼容版本的 JavaScript;包括:语法转换、源代码转换等;
// 例如箭头函数转换
[1, 2, 3].map((n) => n + 1);
[1, 2, 3].map(function(n) {
return n + 1;
});
babel
本身可以作为一个独立的工具(和postcss一样),不和webpack等构建工具配置来单独使用。
2. Babel命令行使用
2.1 安装库及基本配置
-
如果我们希望在命令行尝试使用babel,需要安装如下库:
-
@babel/core
:babel的核心代码,必须安装; -
@babel/cli
:可以让我们在命令行使用babel;npm install @babel/cli @babel/core -D
-
-
使用babel来处理我们的源代码:
-
src
:是源文件的目录; -
--out-dir:指定要输出的文件夹dist;
# 指定目录 npx babel src --out-dir dist # 指定文件 npx babel src --out-file dist
-
2.2 插件的使用
比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:
# 安装插件
npm install @babel/plugin-transform-arrow-functions -D
# 指定使用插件
npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions
查看转换后的结果:我们会发现其中 const 并没有转成 var
-
这是因为 plugin-transform-arrow-functions,并没有提供这样的功能;
-
我们需要使用 plugin-transform-block-scoping 来完成这样的功能;
# 安装插件 npm install @babel/plugin-transform-block-scoping -D # 使用插件 npx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions
2.3 Babel的预设preset
就如上述插件的使用而言,仅仅使用两个插件就需要很长的命令行来执行,当转换的内容过多时,设置颇为麻烦,我们可以使用预设(preset)。
# 安装@babel/preset-env预设:
npm install @babel/preset-env -D
# 执行如下命令:
npx babel src --out-dir dist --presets=@babel/preset-env
3.babel-loader
在实际开发中,我们通常会在构建工具中通过配置babel来对其进行使用的,比如在webpack中。
安装依赖:
npm install babel-loader @babel/core -D
webpack.config.js中配置
module: {
rules: [
{
test: /\.js$/,
use: {
loader: "babel-loader"
}
}
]
}
同样的我们必须指定相应使用得插件才能生效:
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
plugins: [
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-block-scoping",
]
presets: [
"@babel/preset-env"
]
}
}
}
3.1 babel-preset
如果我们一个个去安装使用插件,那么需要手动来管理大量的babel插件,我们可以直接给webpack提供一个 preset,webpack会根据我们的预设来加载对应的插件列表,并且将其传递给babel。
比较常见的预设有三个:
- env
- react
- Typescript
安装preset-env:
npm install @babel/preset-env -D
使用:
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env"
]
}
}
}
4. Babel的配置文件
像之前的postcss配置一样,我们可以将babel的配置信息放到一个独立的文件中---babel.config.js
// babel.config.js
module.exports = {
presets: [
"@babel/preset-env"
]
}
webpack.config.js
{
test: /\.js$/,
loader: "babel-loader"
}
随后进行打包npm run build,即可发现代码转换成功。
六、webpack中Vue代码的打包
1. JS代码
首先下载Vue包。(Vue3)
npm i vue
-
src目录下的
main.js
import { createApp } from "vue"; // Vue代码 createApp({ template: ` <h2>Vue3代码</h2> <h4>{{msg}}</h4> `, data() { return { msg: '你好,陈平安' } } }).mount('#app');
-
index.html
<!DOCTYPE html> <html lang=""> <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"> <link rel="icon" href="./favicon.ico"> <title>哈哈哈哈</title> <script defer src="js/bundle.js"></script></head> <body> <noscript> <strong>We're sorry but 哈哈哈哈 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>
-
webpack.config,js
const path = require("path"); module.exports = { mode: "development", // 设置source-map, 建立js映射文件, 方便调试代码和错误 devtool: "source-map", entry: "./src/main.js", output: { path: path.resolve(__dirname, "./build"), filename: "js/bundle.js", } }
1.1 控制台警告
打包过后运行html文件,发现页面上并无效果。打开控制台会有如下警告:
2. Vue打包后的不同版本
- vue(.runtime).global(.prod).js:
- 括号表示可省略,
runtime
表示运行时,.prod
表示生成阶段代码处于压缩状态; - 通过浏览器中的
script src= "......">
直接使用; - 通过CDN引入和下载的VueJS的版本就是这个版本;
- 会暴露一个全局的Vue来使用;
- 括号表示可省略,
- vue(.runtime).esm-browser(.prod).js:
- 用于通过原生 ES 模块导入使用 (在浏览器中通过
<script type="module">
来使用)。
- 用于通过原生 ES 模块导入使用 (在浏览器中通过
- vue(.runtime).esm-bundler.js:
- 用于 webpack,rollup 和 parcel 等构建工具;
- 构建工具中默认是vue.runtime.esm-bundler.js;
- 如果我们需要解析模板template,那么需要手动指定vue.esm-bundler.js;
- vue.cjs(.prod).js:
- 服务器端渲染使用;
- 通过require()在Node.js中使用;
3. 运行时+编译器 vs 仅运行时
- 在Vue的开发过程中我们有三种方式来编写DOM元素:
template模板
的方式(之前经常使用的方式);render函数
的方式,使用h函数来编写渲染的内容;- 通过
.vue文件
中的template来编写模板;
- 它们的模板分别是如何处理的呢?
- 方式一和方式三的template都需要有特定的代码来对其进行解析:
- 方式一中的template我们必须要通过源码中一部分代码来进行编译;
- 方式三
.vue文件
中的template可以通过在vue-loader
对其进行编译和处理;
- 方式二中的h函数可以直接返回一个虚拟节点,也就是Vnode节点;
- 方式一和方式三的template都需要有特定的代码来对其进行解析:
- 所以,Vue在让我们选择版本的时候分为 运行时+编译器 、仅运行时
运行时+编译器
包含了对template模板的编译代码,更加完整,但是也更大一些;仅运行时
没有包含对template版本的编译代码,相对更小一些;
因此解决上面控制台警告就得更改引入的Vue版本:
main.js
// import { createApp } from "vue"; // 无页面效果+控制台警告 // 指定 运行时+编译器 的Vue版本 import { createApp } from "vue/dist/vue.esm-bundler"; // Vue代码 createApp({ template: ` <h2>Vue3代码</h2> <h4>{{msg}}</h4> `, data() { return { msg: '你好,陈平安' } } }).mount('#app');
⬇⬇⬇⬇⬇⬇⬇
3.1 其实控制台上还有一个警告:
点击链接查看详情,进入到GIthub文档:
上述文档表达的意思:
从Vue3.0.0版本开始,使用
esm-bundler
打包,需要设置全局特征标志
__VUE_OPTIONS_API__
,表示是否是否支持Options API,默认为TRUE__VUE_PROD_DEVTOOLS__
,表示devtools调试工具是否支持生产环境,默认FALSE这些虽不用配置vue也能正常打包,但是强烈建议手动进行配置
webpack可以在DefinePlugin中配置
webpack.config.js
const { DefinePlugin } = require("webpack");
module.exports = {
module: {
rules: [
// ... 其它规则
]
},
plugins: [
// 添加
new DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false
})
]
}
4. .vue
文件的打包
4.1 新建App.vue
文件
<template>
<div>
<h2>我是Vue渲染出来的</h2>
<h2>{{ title }}</h2>
</div>
</template>
<script>
export default {
data() {
return {
title: 'Hello World',
};
}
};
</script>
<style scoped>
h2 {
color: red;
}
</style>
main.js
import { createApp } from "vue/dist/vue.esm-bundler";
import App from "./vue/App.vue";
// vue代码
createApp(App).mount('#app');
4.2 打包出错
终端提示我们需要合适的Loader来处理App.vue
文件。
- 使用
vue-loader
:
# 安装
# npm install vue-loader -D
npm install -D vue-loader vue-template-compiler
- 在
webpack.config.js
配置
{
test: /\.vue$/,
use: "vue-loader"
}
- 打包依然会报错,提示你需要使用VueLoaderPlugin插件
// webpack.config.js 配置:
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin()
]
}
🎈🎈🎈
通过使用
vue-loader
加载Vue文件后,我们在之前main.js
中引入的vue版本为:import { createApp } from "vue/dist/vue.esm-bundler";
现由于Vue文件会交给
vue-loader
和插件VueLoaderPlugin
处理,因此,我们可以直接引入vue而不会出错import { createApp } from "vue";
七、devServe
目前我们开发的代码,为了运行需要有两个操作:
npm run build
,编译相关的代码;- 通过
live server
或者直接通过浏览器,打开index.html代码,查看效果;这个过程经常操作会影响我们的开发效率,我们希望可以做到,当文件发生变化时,可以自动的完成编译和展示;
为了完成自动编译,webpack提供了几种可选的方式:
- webpack watch mode;
- webpack-dev-server(常用);
- webpack-dev-middleware;
1. Webpack watch
webpack给我们提供了watch模式:
- 在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,那么代码将被重新编译;
- 我们不需要手动去运行
npm run build
指令了;
开启watch:
-
方式一:在导出的配置中(webpack.config.js),添加
watch: true
;module.exports = { // 监听文件改变 watch: true, }
-
方式二:在启动webpack的命令中,添加
--watch
的标识;package.json
{ "scripts": { "build": "webpack --watch" }, }
2.webpack-dev-server
上面的方式可以监听到文件的变化,但是事实上它本身是没有自动刷新浏览器的功能的:
- 目前我们可以在VSCode中使用插件
live-server
来完成这样的功能;- 但是插件的实现并不适用所有编辑器,我们想具备
live reloading(实时重新加载)
的功能,就需要另外一个工具webpack-dev-server
2.1 基本使用
-
安装
npm install webpack-dev-server -D
-
基本使用
package.json
{ "scripts": { "serve": "webpack serve" }, }
终端运行:
# 运行 npm run serve
webpack-dev-server 在编译之后不会写入到任何输出文件
,而是将 bundle 文件保留在内存
中:
事实上webpack-dev-server使用了一个库叫memfs,将文件内容读取到内容存中。
2.2 配置选项
2.2.1 static
该配置项允许配置从目录提供静态文件的选项(默认是 'public' 文件夹)。将其设置为 false
以禁用:
webpack.config.js
module.exports = {
devServer: {
// static: false, // 禁用
// 告诉dev serve从哪取未被webpack打包的静态资源,
// 浏览器加载html文件,html文件中引入了经过webpack打包的js文件和未经过打包打包的文件
// Content not from webpack is served from
// 而未被webpack打包的内容若引入路径为'./abc.js',则会默认从public目录下寻找abc.js文件
// 如要指定相应的资源路径,则可以设置static
static: ["./public", "./asset"],
static: {
directory: path.join(__dirname, 'public'),
}
}
}
2.2.2 模块热替换(HMR)
2.2.2.1 介绍:
HMR的全称是Hot Module Replacement
,翻译为模块热替换;
模块热替换是指在 应用程序运行过程中,进行替换、添加、删除模块
,而无需重新刷新整个页面
;
HMR通过如下几种方式,来提高开发的速度:
不重新加载整个页面
,这样可以保留某些应用程序的状态不丢失
;- 只更新
需要变化的内容
,节省开发的时间
; - 修改了
css、js源代码
,会立即在浏览器更新
,相当于直接在浏览器的devtools中直接修改样式;
2.2.2.2 使用:
webpack.config.js
module.exports = {
devServer: {
contentBase: "./public",
hot: true
}
}
仅配置hot: true
,不能实现热模块替换。还需要指定需要监听的模块是哪个:
-
main.js
-------webpack入口文件import App from './vue/App.vue'; import "./js/element"; // 监听element.js模块 if (module.hot) { module.hot.accept("./js/element.js", () => { console.log('elementJS模块更新了'); }) } const app = createApp(App); app.mount("#app");
-
element.js
console.log("更改前"); // npm run serve,新增一句console.log,热更新后在控制台会在之前输出的内容后 // 继续打印 “更改后” 和 “elementJS模块更新了” console.log("更改后");
问题:在开发其他项目时,我们是否需要经常手动去写入 module.hot.accpet相关的API呢?
在Vue、React中,其实已经有成熟的解决方案:
vue开发中,我们使用vue-loader,此loader支持vue组件的HMR,提供开箱即用的体验;
之前已经使用过
vue-loader
。因此可直接修改Vue文件,保存后前往浏览器查看效果。react开发中,有React Hot Loader,实时调整react组件(目前React官方已经弃用了,改成使用reactrefresh);
2.2.2.3 HMR的原理
webpack-dev-server
会创建两个服务:提供静态资源的服务(express)
和Socket服务(net.Socket)
;
express server
负责直接提供静态资源的服务
(打包后的资源直接被浏览器请求和解析);Socket Server
,是一个socket的长连接- 长连接有一个最好的好处是
建立连接后双方可以通信
(服务器可以直接发送文件到客户端) - 当服务器
监听到对应的模块发生变化时
,会生成两个文件.json(manifest文件)和.js文件(update chunk)
; - 通过长连接,可以直接
将这两个文件主动发送给客户端
(浏览器); - 浏览器
拿到两个新的文件
后,通过HMR runtime机制,加载这两个文件
,并且针对修改的模块进行更新
;
- 长连接有一个最好的好处是
2.2.3 hotOnly、host配置
-
host设置主机地址:
- 默认值是localhost;
- 如果希望其他地方也可以访问,可以设置为 0.0.0.0;
-
localhost 和 0.0.0.0 的区别:
- localhost:本质上是一个域名,通常情况下会被解析成127.0.0.1;
- 127.0.0.1:回环地址(Loop Back Address),表达的意思其实是我们主机自己发出去的包,直接被自己接收;
- 正常的数据库包经常
应用层 - 传输层 - 网络层 - 数据链路层 - 物理层
; - 而回环地址,是在网络层直接就被获取到了,是不会经常数据链路层和物理层的;
- 比如我们监听 127.0.0.1时,在同一个网段下的主机中,通过ip地址是不能访问的;
- 正常的数据库包经常
- 0.0.0.0:监听IPV4上所有的地址,再根据端口找到不同的应用程序;
- 比如我们监听 0.0.0.0时,在同一个网段下的主机中,通过ip地址是可以访问的;
-
webpack.config.js
设置module.exports = { devServer: { static: ['./src/js', './public'], hot: true, // 设置主机地址,默认为localhost即127.0.0.1 host: "0.0.0.0" } }
2.2.4 port、open、compress
-
port
设置监听的端口,默认情况下是8080 -
open
是否打开浏览器:- 默认值是false,设置为true会打开浏览器;
- 也可以设置为类似于 Google Chrome等值;
-
compress
是否为静态文件开启gzip compression:- 默认值是false,可以设置为true;
- 设置为True时,webpack dev serve会将打包的资源进行gzip压缩
-
webpack.config.js
module.exports = { devServer: { static: ['./src/js', './public'], hot: true, // 设置主机地址,默认为localhost即127.0.0.1 host: "0.0.0.0", port: 9999, // 默认是8080 open: true, // 默认是false compress: true // 默认是false } }
2.2.5 Proxy
proxy是我们开发中非常常用的一个配置选项,它的目的设置代理来解决跨域访问的问题:
- 比如我们的一个api请求是
http://localhost:8888
,但是本地启动服务器的域名是http://localhost:8000
,这个时候发送网络请求就会出现跨域的问题; - 那么我们可以将请求
先发送到一个代理服务器,代理服务器和API服务器没有跨域的问题
,就可以解决我们的跨域问题
了;
2.2.5.1 配置
target
:表示的是代理到的目标地址,比如 /api/moment会被代理到http://localhost:8888/api/moment
;pathRewrite
:默认情况下,我们的 /api 也会被写入到URL中,如果希望删除,可以使用pathRewrite;secure
:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false;changeOrigin
:它表示是否更新代理后请求的 headers 中的host地址;- 因为我们真实的请求,其实是需要通过
http://localhost:8888
来请求的; - 但是因为使用了Proxy代理,默认情况下它的值是
http://localhost:8000
; - 如果我们需要修改,那么可以将changeOrigin设置为true即可;
- 因为我们真实的请求,其实是需要通过
webpack.config.js
module.exports = {
devServer: {
proxy: {
// "/api": "http://localhost:8888",
"/api": {
target: 'http://localhost:8888',
pathRewrite: { '^/api': '' },
changeOrigin: true,
secure: false
}
}
}
}
main.js
// 使用 axios 进行网络请求
import axios from 'axios'
// 当前前端启动地址为 'http://localhost:8000'
axios.get('/api/mement').then(res => {
console.log(res.data)
}).catch(err => {
console.log(err)
})
// 这里请求地址为 '/api/mement',因为配置了Proxy代理,
// 所以请求地址被改为 http://localhost:8888/mement
// 未设置pathRewrite 则为 http://localhost:8888/api/mement
2.2.6 historyApiFallback
使用 HTML5 History API
(历史History路由)时,可能必须提供 index.html 页面来代替任何 404 响应。通过将 devServer.historyApiFallback
设置为 true 来启用它。
historyApiFallback
是开发中一个非常常见的属性,它主要的作用是解决SPA页面在路由跳转之后,然后进行页面刷新时,返回404的错误。
-
boolean值:默认是false
-
如果设置为true,那么在刷新时,返回404错误时,会自动返回 index.html 的内容;
module.exports = { //... devServer: { historyApiFallback: true, }, };
-
-
object类型的值,可以配置rewrites属性(了解):
-
可以配置from来匹配路径,决定要跳转到哪一个页面;
module.exports = { //... devServer: { historyApiFallback: { rewrites: [ { from: /^\/$/, to: '/views/landing.html' }, { from: /^\/subpage/, to: '/views/subpage.html' }, { from: /./, to: '/views/404.html' }, ], }, }, };
-
八、resolve模块解析
1.resolve的介绍
1.1 resolve用于设置模块如何被解析:
- 在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库;
- resolve可以帮助webpack从每个 require/import 语句中,找到需要引入到合适的模块代码;
- webpack 使用
enhanced-resolve
来解析文件路径;
1.2 webpack能解析三种文件路径:
- 绝对路径
- 由于已经获得文件的绝对路径,因此不需要再做进一步解析。
- 相对路径
- 在这种情况下,使用
import
或require
的资源文件所处的目录,被认为是上下文目录; - 在
import/require
中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径;
- 在这种情况下,使用
- 模块路径
- 在
resolve.modules
中指定的所有目录检索模块;- 默认值是
['node_modules']
,所以默认会从 node_modules 中查找文件;
- 默认值是
- 我们可以通过设置别名的方式来替换初识模块路径,具体后面讲解
alias
的配置;
- 在
1.3 确实文件还是文件夹
- 如果是一个文件:
- 如果文件具有扩展名,则直接打包文件;
- 否则,将使用
resolve.extensions
选项作为文件扩展名解析;
- 如果是一个文件夹:
- 会在文件夹中根据
resolve.mainFiles
配置选项中指定的文件顺序查找;resolve.mainFiles
的默认值是 ['index'];- 再根据
resolve.extensions
来解析扩展名;
- 会在文件夹中根据
2. 配置
2.1 extensions
extensions是解析到文件时自动添加扩展名
:
- 默认值是
['.wasm', '.mjs', '.js', '.json']
; - 所以如果我们代码中想要添加加载 .vue 或者 jsx 或者 ts 等文件时,我们必须自己写上扩展名;
module.exports = {
//...
resolve: {
extensions: ['.js', '.json', '.mjs', '.vue', '.ts'],
},
};
请注意,以上这样使用 resolve.extensions
会 覆盖默认数组,这就意味着 webpack 将不再尝试使用默认扩展来解析模块。然而你可以使用 '...'
访问默认拓展名:
module.exports = {
//...
resolve: {
extensions: ['.ts', '...'],
},
};
2.2 alias
配置别名alias
:
- 特别是当我们项目的目录结构比较深的时候,或者一个文件的路径可能需要
../../../
这种路径片段; - 我们可以给某些常见的路径起一个别名;
const path = require('path');
module.exports = {
//...
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
js: path.resolve(__dirname, 'src/js/'),
utils: path.resolve(__dirname, 'src/utils/'),
},
},
};
路径引入:
// 配置别名前:
import utilFormDate from '../../utils/utilFormDate';
// 配置别名后:
import utilFormDate from 'utils/utilFormDate';
import utilFormDate from '@/utils/utilFormDate';
九、区分开发环境的webpack配置
1. 目前状况
目前我们所有的webpack配置信息都是放到一个配置文件中的:webpack.config.js
:
- 当配置越来越多时,这个文件会变得越来越不容易维护;
并且某些配置是在开发环境需要使用的,某些配置是在生成环境需要使用的,当然某些配置是在开发和生成环境都会使用的
;- 所以,我们最好对配置进行划分,方便我们维护和管理;
2.解决方案
2.1 编写不同配置文件
在根目录下新建config
的目录
├── config (不同配置文件夹)
| ├── webpack.comm.conf.js
| ├── webpack.dev.conf.js
| ├── webpack.prod.conf.js
-
webpack.comm.conf.js
存放开发和生产环境下共有的配置
-
webpack.dev.conf.js
存放开发环境下的webpack配置,并通过 merge 函数进行合并配置
-
webpack.prod.conf.js
存放生产环境下的webpack配置,并通过 merge 函数进行合并配置
-
merge 函数进行合并配置
- 下载 webpack-merge 工具
npm i webpack-merge -D
- 配置
// 合并公有webpack配置 const { merge } = require('webpack-merge'); const commonConfig = require('./webpack.common.config'); module.exports = merge(commonConfig, { // 相应环境下的 webpack 配置 });
2.2 为webpack运行指定配置文件
在 package.json
中进行脚本配置
{
"scripts": {
"dev": "webpack serve --config ./config/webpack.dev.config.js",
"prod": "webpack --config ./config/webpack.prod.config.js"
},
}