个人将前端开发分为三个阶段:
Web1.0前端主要工作:
伴随着AJAX的诞生,前端的工作模式也发生了很大变化,前端不仅仅是展示界面,而且还可以管理数据以及和用户进行数据的交互。在这样的阶段过程中,诞生了像jQuery这样的优秀前端工具库。
在这个阶段中,前端的工作变得更加多样化和复杂化,例如现在前端不仅仅需要开发PC端Web界面,还有移动端的Web界面,以及小程序和公众号,甚至有些时候还需要做App和桌面客户端。
伴随着需要做的事情越来越多,流程也越来越复杂,因此就会出现一些问题。比如说:
现代Web开发“问题”
采用模块化开发
使用新特性提高效率保证安全性
实时监听开发过程使用热更新
项目结果打包压缩优化
需要有一个工具站出来解决问题,可以让开发者在入口的地方随心所欲,用个人喜欢的技术栈完成开发,从而不需要关系过程,但是最终的结果可以在浏览器上正常展示,因此这里就会用到打包工具。当前Vue、React、Angular本身集成Webpack。
Webpack定义:为现代JavaScript应用提供静态模块打包
Webpack功能:
构件如图目录结构,并编码
在web server中进行预览,发现虽然在index.html中使用了type="module"
,但是依然无法同时识别ES Module和CommonJS。
此时,提前安装好的Webpack就起了作用,在命令行终端输入:Webpack,这时发现目录中输出了dist目录。这里需要注意,Webpack打包会默认找到项目目录下的src目录,并且找到index.js作为入口文件,对依赖进行打包处理,并输出到dist目录中,输出结果默认为main.js。如下:
(() => { var o = { 50: o => { o.exports = () => ({ name: "zce", age: 40 }) } }, e = {}; function r(s) { var t = e[s]; if (void 0 !== t) return t.exports; var n = e[s] = { exports: {} }; return o[s](n, n.exports, r), n.exports } (() => { "use strict"; const o = r(50); console.log(30), console.log(100), console.log(o()) })() })();
观察main.js,发现当前Webpack并未解决ES6+的语法兼容问题
此时将index.html中引入的js文件变更为dist/main.js。
<body>
<script src="./dist/main.js">script>
body>
yarn Webpack --entry ./src/main.js --output-path ./build
,其中--entry
指定入口文件,--output-path
指定输入路径...
"scripts": {
"build": "Webpack --entry ./src/main.js --output-path ./build"
}
...
通过命令行运行yarn build
进行打包
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist') // 必须使用绝对路径,不然会Webpack抛出错误
}
}
目录结构:
在index.js中引入lg.js,随后在index.html中引入dist目录下的main.js。Webpack在打包过程中会自动寻找依赖关系并引入,最终打包为main.js。
上文提到可以使用命令行参数对入口文件、输出目录及输出文件名做配置,这里使用–config参数可以对Webpack配置文件进行自定义。例如:
...
{
"scripts" : {
"build": "Webpack --config my-Webpack-config.js"
}
}
...
此时Webpack就会从my-Webpack-config.js读取配置进行打包处理。
index.html中script引入打包后的main.js
index.js入口文件中引入login.js
// login.js
function createDom() {
const h2 = document.createElement('h2')
h2.innerHTML = '拉勾教育'
h2.className = 'title'
return h2
}
document.body.appendChild(createDom())
此时,需要.title添加css样式
/*
login.css
*/
.title {
color: red
}
在login.js中引入login.css,并进行Webpack打包,此时会抛出异常,css文件并不是一个模块。
import '../css/login.css'
function login() {
const oH2 = document.createElement('h2')
oH2.innerHTML = '拉勾教育前端'
oH2.className = 'title'
return oH2
}
document.body.appendChild(login())
loader是一个模块,内部使用js实现具体逻辑,比如现在需要一个loader让login.css代码转换为Webpack能识别的模块。
安装css-loader
yarn add css-loader
行内使用loader,多个loader使用英文**!**进行分隔
import 'css-loader!../css/login.css'
function login() {
const oH2 = document.createElement('h2')
oH2.innerHTML = '拉勾教育前端'
oH2.className = 'title'
return oH2
}
document.body.appendChild(login())
重新执行yarn webpack
,虽然没有语法报错,但是样式并未生效。还需要使用一个style-loader
。
配置文件中使用css-loader
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
// {
// test: /\.css$/, // 一般就是一个正则表达式,用于匹配我们需要处理的文件类型
// use: [
// {
// loader: 'css-loader'
// }
// ]
// },
// {
// test: /\.css$/,
// loader: 'css-loader'
// },
{
test: /\.css$/,
use: ['css-loader']
}
]
}
}
然后回到webpack.config.js下,将入口文件的路径指向新创建的css文件。随后配置loader组件,test值为正则表达式/.css$/,use配置一个数组,分别为style-loader
以及style-loader
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.css',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
},
module: {
// rules数组针对于其它资源模块的加载规则,每个规则对象的都需要设置两个属性。
rules: [
{
test: /.css$/, // test用来去匹配在打包过程当中所遇到的文件路径
// use用来去指定我们匹配到的文件,需要去使用的loader
use: [
'style-loader',
'css-loader'
]
}
]
}
}
命令行启动,yarn Webpack,通过serve . 运行,在浏览器中访问就可以看到我们的css生效了。
ps:use中,如果配置了多个loader,其执行顺序是从数组最后一个元素往前执行。所以这里一定要把css-loader放到最后,因为我们必须要先通过css-loader把css代码转换为模块才可以正常打包。
style-loader工作代码在bundle.js中,部分代码如下:
loader是webpack实现整个前端模块化的核心,通过不同的loader就可以实现加载任何类型的资源。
在项目中使用less编写css代码,首先在login.js中正常引入login.less,正常使用Webpack进行编译,发现报错基本与未使用css-loader相同。Webpack默认不支持less文件的编译,所以按照思路,需要先将less文件编译为css文件,然后使用css-loader与style-loader搭配使用,将css样式引入到index.html。下面进行尝试:
首先安装less,尝试把login.less编译为index.css
npm i less -D # 安装less
npx less ./src/css/login.less index.css # 使用less将login.less编译为index.css
// import 'css-loader!../css/login.css'
import '../css/login.css'
import '../css/login.less'
function login() {
const oH2 = document.createElement('h2')
oH2.innerHTML = '拉勾教育前端'
oH2.className = 'title'
return oH2
}
document.body.appendChild(login())
此时运行Webpack进行打包,发现会抛出错误,错误类型与上面提到的Webpack无法编译css文件时相同。
回到初始思路上,我们需要less-loader将less文件编译为css文件,其次使用css-loader搭配style-loader,将css样式编译至html文件中,所以需要进行配置,配置思路与css相同。如下代码:
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
}
}
ps:记住loader加载使用顺序是:从右到左,从下到上
https://caniuse.com/
.browserslistrc 是在不同的前端工具之间共用目标浏览器和 node 版本的配置文件。它主要被以下工具使用:
Autoprefixer
Babel
post-preset-env
eslint-plugin-compat
stylelint-unsupported-browser-features
postcss-normalize
Webpack默认会安装browserlistrc
前端工程需要在package.json中配置
{
"browserslist": [
"last 1 version",
"> 1%",
"maintained node versions",
"not dead"
]
}
也可在.browserslistrc文件中配置
# 注释是这样写的,以#号开头
last 1 version #最后的一个版本
> 1% #代表全球超过1%使用的浏览器
maintained node versions #所有还被 node 基金会维护的 node 版本
not dead
不配置默认为:> 0.5%, last 2 versions, Firefox ESR, not dead
在当前目录下查询目标浏览器npx browserslist
查询条件列表
你可以用如下查询条件来限定浏览器和 node 的版本范围(大小写不敏感):
> 5%: 基于全球使用率统计而选择的浏览器版本范围。>=,<,<=同样适用。
> 5% in US : 同上,只是使用地区变为美国。支持两个字母的国家码来指定地区。
> 5% in alt-AS : 同上,只是使用地区变为亚洲所有国家。这里列举了所有的地区码。
> 5% in my stats : 使用定制的浏览器统计数据。
cover 99.5% : 使用率总和为99.5%的浏览器版本,前提是浏览器提供了使用覆盖率。
cover 99.5% in US : 同上,只是限制了地域,支持两个字母的国家码。
cover 99.5% in my stats :使用定制的浏览器统计数据。
maintained node versions :所有还被 node 基金会维护的 node 版本。
node 10 and node 10.4 : 最新的 node 10.x.x 或者10.4.x 版本。
current node :当前被 browserslist 使用的 node 版本。
extends browserslist-config-mycompany :来自browserslist-config-mycompany包的查询设置
ie 6-8 : 选择一个浏览器的版本范围。
Firefox > 20 : 版本高于20的所有火狐浏览器版本。>=,<,<=同样适用。
ios 7 :ios 7自带的浏览器。
Firefox ESR :最新的火狐 ESR(长期支持版) 版本的浏览器。
unreleased versions or unreleased Chrome versions : alpha 和 beta 版本。
last 2 major versions or last 2 ios major versions :最近的两个发行版,包括所有的次版本号和补丁版本号变更的浏览器版本。
since 2015 or last 2 years :自某个时间以来更新的版本(也可以写的更具体since 2015-03或者since 2015-03-10)
dead :通过last 2 versions筛选的浏览器版本中,全球使用率低于0.5%并且官方声明不在维护或者事实上已经两年没有再更新的版本。目前符合条件的有 IE10,IE_Mob 10,BlackBerry 10,BlackBerry 7,OperaMobile 12.1。
last 2 versions :每个浏览器最近的两个版本。
last 2 Chrome versions :chrome 浏览器最近的两个版本。
defaults :默认配置> 0.5%, last 2 versions, Firefox ESR, not dead。
not ie <= 8 : 浏览器范围的取反。
可以添加not在任和查询条件前面,表示取反
注意:
1.可以使用如下写法,从另外一个输出 browserslist 配置的包导入配置数据:
"browserslist": [
"extends browserslist-config-mycompany"
]
为了安全起见,额外的配置包只支持前缀 browserslist-config- 的包命名. npm包作用域也同样支持 @scope/browserslist-config,例如:
@scope/browserslist-config or @scope/browserslist-config-mycompany.
#When writing a shared Browserslist package, just export an array.
#browserslist-config-mycompany/index.js:
module.exports = [
'last 1 version',
'> 1%',
'ie 10'
]
2.环境的差异化配置
你可以为不同的环境配置不同的浏览器查询条件。Browserslist 将依赖BROWSERSLIST_ENV 或者 NODE_ENV查询浏览器版本范围。如果两个环境变量都没有配置正确的查询条件,那么优先从 production 对应的配置项加载查询条件,如果再不行就应用默认配置。
在 package.json:
"browserslist": {
"production": [
"> 1%",
"ie 10"
],
"development": [
"last 1 chrome version",
"last 1 firefox version"
]
}
在配置文件.broswerslistrc
中
[production staging]
> 1%
ie 10
[development]
last 1 chrome version
last 1 firefox version
官网说:“PostCSS,一个使用 JavaScript 来处理CSS的框架”。这句话高度概括了 PostCSS 的作用,但是太抽象了。按我理解,PostCSS 主要做了三件事:
parse
:把 CSS 文件的字符串解析成抽象语法树(Abstract Syntax Tree)的框架,解析过程中会检查 CSS 语法是否正确,不正确会给出错误提示。runPlugin:
执行插件函数。PostCSS 本身不处理任何具体任务,它提供了以特定属性或者规则命名的事件。有特定功能的插件(如 autoprefixer、CSS Modules)会注册事件监听器。PostCSS 会在这个阶段,重新扫描 AST,执行注册的监听器函数。generate
: 插件对 AST 处理后,PostCSS 把处理过的 AST 对象转成 CSS string。
「如果没有插件」,那么初始传入的 CSS string 和 generate 生成的 CSS string 是一样的。由此可见,PostCSS 本身并不处理任何具体的任务,只有当我们为其附加各种插件之后,它才具有实用性。
CSS 语法简述
CSS 规则集(rule-set)由选择器和声明块组成:
五类对象
AST 用五类对象描述 CSS 语法。这里举个具体的例子,再打印出对应的 AST 结果,对照了解 AST 五类对象和 CSS 语法的对应关系。
app.css 文件中写如下内容:
@import url('./app-02.css');
.container {
color: red;
}
Declaration(声明) 对象
Declaration 对象用来描述 CSS 中的每一条声明语句。
上边 CSS 文件中的color: red;
会被描述成如下对象:
{
parent: Rule, // 外层的选择器被转译成 Rule 对象,是当前声明对象的 parent
prop: "color", // prop 字段记录声明的属性
raws: { // raws 字段记录声明前、后的字符串,声明属性和值之间的字符串,以及前边语句是否分号结束。
before: '\n ', // raws.before 字段记录声明前的字符串
between: ': ', // raws.between 字段记录声明属性和值之间的字符串
},
source: { // source 字段记录声明语句的开始、结束位置,以及当前文件的信息
start: { offset: 45, column: 3, line: 4 },
end: { offset: 55, column: 13, line: 4 },
input: Input {
css: '@import url('./app-02.css');\n\n.container {\n color: red;\n}',
file: '/Users/admin/temp/postcss/app.css',
hasBOM: false,
Symbol(fromOffsetCache): [0, 29, 30, 43, 57]
}
},
Symbol('isClean'): false, // Symbol(isClean) 字段默认值都是 false,用于记录当前对象关联的 plugin 是否执行。plugin 会在后续解释
Symbol('my'): true, // Symbol(my) 字段默认值都是 true,用于记录当前对象是否是对应对象的实例,如果不是,可以根据类型把对象的属性设置为普通对象的 prototype 属性
type: 'decl', // type 记录对象类型,是个枚举值,声明语句的 type 固定是 decl
value: "red" // value 字段记录声明的值
}
每个字段的含义和功能已经以注释的形式进行了解释。
Rule 对象
Rule 对象是描述选择器的。
上边 app.css 文件中.container经过 postcss 转译后的对象是(每个字段的含义和功能已经以注释的形式进行了解释):
{
nodes: [Declaration], // nodes 记录包含关系,Rule 对象包含 Declaration 对象
parent: Root, // 根对象是 Root 对象,是当前声明对象的 parent
raws: { // raws 字段记录如下
before: '\n\n', // raws.before 字段记录选择器前的字符串
between: ' ', // raws.between 字段记录选择器和大括号之间的字符串
semicolon: true, // raws.semicolon 字段记录前置声明语句是正常分号结束
after: '\n' // raws.after 字段记录最后一个声明和结束大括号之间的字符串
},
selector:'.container', // selector 记录 selector
source: { // source 字段记录选择器语句的开始、结束位置,以及当前文件的信息
start: { offset: 30, column: 1, line: 3 },
input: Input {
css: '@import url('./app-02.css');\n\n.container {\n color: red;\n}',
file: '/Users/admin/temp/postcss/app.css',
hasBOM: false,
Symbol(fromOffsetCache): [0, 29, 30, 43, 57]
},
end: { offset: 57, column: 1, line: 5 }
},
Symbol('isClean'): false, // Symbol(isClean) 字段默认值都是 false,用于记录当前对象关联的 plugin 是否执行。plugin 会在后续解释
Symbol('my'): true, // Symbol(my) 字段默认值都是 true,用于记录当前对象是否是对应对象的实例,如果不是,可以根据类型把对象的属性设置为普通对象的 prototype
type: 'rule' // type 记录对象类型,是个枚举值,声明语句的 type 固定是 rule
}
Root 对象
Root 对象是 AST 对象的根对象。
type 记录当前对象的类型
nodes 属性记录子节点对应对象的实例。
上边 app.css 文件中 root 对象是(每个字段的含义和功能已经以注释的形式进行了解释):
{
nodes: [AtRule, Rule], // nodes 记录子对象(选择器和 @开头的对象),AtRule 对象会在后边提到
raws: { // raws 字段记录如下
semicolon: false, // raws.semicolon 最后是否是分号结束
after: '' // raws.after 最后的空字符串
},
source: { // source 字段记录根目录语句的开始,以及当前文件的信息
start: { offset: 0, column: 1, line: 1 },
input: Input {
css: '@import url('./app-02.css');\n\n.container {\n color: red;\n}',
file: '/Users/admin/temp/postcss/app.css',
hasBOM: false,
Symbol(fromOffsetCache): [0, 29, 30, 43, 57]
}
},
Symbol('isClean'): false, // Symbol(isClean) 字段默认值都是 false,用于记录当前对象关联的 plugin 是否执行。plugin 会在后续解释
Symbol('my'): true, // Symbol(my) 字段默认值都是 true,用于记录当前对象是否是对应对象的实例,如果不是,可以根据类型把对象的属性设置为普通对象的 prototype
type: 'root' // type 记录对象类型,是个枚举值,声明语句的 type 固定是 root
}
AtRule 对象
CSS 中除了选择器,还有一类语法是 @
开头的,例如 @import
、@keyframes
、@font-face
,PostCSS 把这类语法解析成 AtRule 对象。
例如 @import url("./app-02.css");
将被解析成如下对象:
{
name: "import", // name 记录 @ 紧跟着的单词
params: "url('./app-02.css')", // params 记录 name 值
parent: Root, // parent 记录父对象
raws: { // raws 字段记录如下
before: '', // raws.before 记录 @语句前的空字符串
between: '', // raws.between 记录 name 和 { 之间的空字符串
afterName: '', // raws.afterName 记录 name 和 @ 语句之间的空字符串
after: '', // raws.after 记录大括号和上一个 rule 之间的空字符串
semicolon: false // raws.semicolon 上一个规则是否是分号结束
},
source: { // source 字段记录@语句的开始,以及当前文件的信息
start: { offset: 0, column: 1, line: 1 },
end: { offset: 27, column: 28, line: 1 },
input: Input {
css: '@import url('./app-02.css');\n\n.container {\n color: red;\n}',
file: '/Users/admin/temp/postcss/app.css',
hasBOM: false,
Symbol(fromOffsetCache): [0, 29, 30, 43, 57]
}
},
Symbol('isClean'): false, // Symbol(isClean) 字段默认值都是 false,用于记录当前对象关联的 plugin 是否执行。plugin 会在后续解释
Symbol('my'): true, // Symbol(my) 字段默认值都是 true,用于记录当前对象是否是对应对象的实例,如果不是,可以根据类型把对象的属性设置为普通对象的 prototype
type: 'atrule' // type 记录对象类型,是个枚举值,声明语句的 type 固定是 atrule
}
Comment 对象
css 文件中的注释被解析成 Comment 对象。text 字段记录注释内容。/* 你好 */
被解析成:
{
parent: Root, // parent 记录父对象
raws: { // raws 字段记录如下
before: '', // raws.before 记录注释语句前的空字符串
left: ' ', // raws.left 记录注释语句左侧的空字符串
right: ' ' // raws.right 记录注释语句右侧的空字符串
},
source: { // source 字段记录注释语句的开始、结束位置,以及当前文件的信息
start: {…}, input: Input, end: {…}
},
Symbol('isClean'): false, // Symbol(isClean) 字段默认值都是 false,用于记录当前对象关联的 plugin 是否执行。plugin 会在后续解释
Symbol('my'): true, // Symbol(my) 字段默认值都是 true,用于记录当前对象是否是对应对象的实例,如果不是,可以根据类型把对象的属性设置为普通对象的 prototype
text: '你好', // text 记录注释内容
type: 'comment' // type 记录对象类型,是个枚举值,声明语句的 type 固定是 comment
}
图解五类对象之间的继承关系
从上一段可以知道,CSS 被解析成 Declaration、Rule、Root、AtRule、Comment 对象。这些对象有很多公共方法,PostCSS 用了面向对象的继承思想,把公共方法和公共属性提取到了父类中。
Root、Rule、AtRule 都是可以有子节点的,都有 nodes 属性,他们三个继承自 Container 类,对 nodes 的操作方法都写在 Container 类中。Container、Declaration、Comment 继承自 Node 类,所有对象都有 Symbol(‘isClean’)、Symbol(‘my’)、raws、source、type 属性,都有toString()、error()等方法,这些属性和方法都定义在 Node 类中。
Container、Node 是用来提取公共属性和方法,不会生成他们的实例。
五个类之间的继承关系如下图所示:
图中没有穷举类的方法,好奇的同学可以看直接看源码文件: https://github.com/postcss/postcss/tree/main/lib 。
把 CSS 语法解析成 AST 对象的具体算法
算法对应源码中位置是:postcss/lib/parser.js
中的parse
方法,代码量不大,可自行查看。
PostCSS 本身并不处理任何具体的任务,只有当我们为其附加各种插件之后,它才具有实用性。
PostCSS 在把 CSS string 解析成 AST 对象后,会扫描一边 AST 对象,每一种 AST 的对象都可以有对应的监听器。在遍历到某类型的对象时,如果有对象的监听器,就会执行其监听器。
第一类监听器
PostCSS 提供的「以特定属性或者规则命名」的事件监听器,如下:
CHILDREAN 代表子节点的事件监听器。
// root
['Root', CHILDREN, 'RootExit']
// AtRule
['AtRule', 'AtRule-import', CHILDREN, 'AtRuleExit', 'AtRuleExit-import']
// Rule
['Rule', CHILDREN, 'RuleExit']
// Declaration
['Declaration', 'Declaration-color', 'DeclarationExit', 'DeclarationExit-color']
// Comment
['Comment', 'CommentExit']
PostCSS 以深度优先的方式遍历 AST 树。
{
Root: (rootNode, helps) => {},
RootExit: (rootNode, helps) => {}
}
{
Rule: (ruleNode, helps) => {},
RuleExit: (ruleNode, helps) => {}
}
['AtRule', 'AtRule-import', CHILDREN, 'AtRuleExit', 'AtRuleExit-import']
。CHILDREAN 代表子节点的事件。```// 函数 { AtRule: (atRuleNode, helps) => {} }// 对象
{
AtRule: {
import: (atRuleNode, helps) => {},
keyframes: (atRuleNode, helps) => {}
}
}
['Declaration', 'Declaration-color', 'DeclarationExit', 'DeclarationExit-color']
。Declaration 没有子对象,只需要执行当前对象的事件,不需要深度执行子对象的事件。// 函数
{
Declaration: (declarationNode, helps) => {}
}
// 对象
{
Declaration: {
color: (declarationNode, helps) => {},
border: (declarationNode, helps) => {}
}
}
第二类监听器
除以特定属性或者规则命名的事件监听器,PostCSS 还有以下四个:
{
postcssPlugin: string,
prepare: (result) => {},
Once: (root, helps) => {},
OnceExit: (root, helps) => {},
}
PostCSS 插件事件的整体执行是:[prepare, Once, ...一类事件,OnceExit]
,postcssPlugin 是插件名称,不是事件监听器。
{
postcssPlugin: "PLUGIN NAME",
prepare(result) {
const variables = {};
return {
Declaration(node) {
if (node.variable) {
variables[node.prop] = node.value;
}
},
OnceExit() {
console.log(variables);
},
};
},
};
{
Once: (root, helps) => {}
}
插件源码截图
此时再看市面上流行的基于 postcss 的工具,有没有醍醐灌顶?
基于 postcss 的插件有很多,可查阅:https://github.com/postcss/postcss/blob/main/docs/plugins.md
generate 的过程依旧是以深度优先的方式遍历 AST 对象,针对不同的实例对象进行字符串的拼接。算法对应源码中位置是:postcss/lib/stringifier.js
中的stringify方法,代码量不大,可自行查看。
css3自动加前缀 -webkit
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader:'postcss-loader',
options:{ // Webpack选项
postcssOptions:{ // loader配置选项
plugins:[
require('autoprefixer')
]
}
}
}
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
}
]
}
}
处理颜色的8进制
color: #12345678后两位用于指定透明度
postcss-preset-env
预设就是插件的集合,postcss-preset-env
已经包含了autoprefixer
,所以可以只使用postcss-preset-env
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader:'postcss-loader',
options:{ // Webpack选项
postcssOptions:{ // loader配置选项
plugins:[
require('postcss-preset-env')
]
}
}
}
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
}
]
}
}
npm i postcss-preset-env -D
单独使用配置文件配置postcss插件
postcss.config.js
module.exports = {
plugins: [
require('postcss-preset-env')
]
}
问题:
test.css的内容如下:
.title {
transition: all .5s;
user-select: none;
}
login.css的内容如下:
/* 导入test.css */
@import './test.css';
.title {
color: #12345678;
}
再次npm run build发现运行之后的test.css里面的代码并没有做兼容性处理。
问题分析:
解决问题:修改Webpack.config.js给css-loader设置一些属性。
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'build.js',
//output必须设置绝对路径,所以这里导入path模块
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
//简写方式
test: /\.css$/,
//先执行style-loader再执行css-loader
//顺序规则,从右往左,从下往上,因为兼容性处理要在css调用之前,所以需要将postcss-loader的配置放在css-loader右边
use: ['style-loader', {
loader: 'css-loader',
options: {
// css-loader工作时,遇到css文件时,再往前找一个loader,即追回到postcss-loader
importLoaders: 1
}
}, 'postcss-loader']
},
{
//简写方式
test: /\.less$/,
//先执行style-loader再执行css-loader
//顺序规则,从右往左,从下往上
use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']
}
]
}
}
再次运行成功。运行结果如下,test.css的内容也被修改成功。
在js文件中引入img图片并输出到页面上
要处理jpg、png等格式的图片,我们也需要有对应的loader: file-loader。file-loader的作用就是帮助我们处理import/require()等方式引入的一个文件资源,并且会将它放到我们输出的文件夹中;当然也可以修改它的名字和所在文件夹
安装file-loader
npm install file-loader -D
Image.js中导入图片并显示在页面上:
import oImgSrc from '../img/01.wb.png'
function packImg() {
// 01 创建一个容器元素
const oEle = document.createElement('div')
// 02 创建 img 标签,设置 src 属性
const oImg = document.createElement('img')
oImg.width = 600
// 写法1:使用require...default取值
// require导入默认一个对象,有一个default的键,代表的导入的内容
// oImg.src = require('../img/01.wb.png').default
// 写法2:lg.Webpack.js配置文件搭配使用,不需要写default取值
// esModule: false // 不转为 esModule
// oImg.src = require('../img/01.wb.png')
// 写法3:使用import导入,不需要写default或者config配置esModule
oImg.src = oImgSrc
oEle.appendChild(oImg)
return oEle
}
document.body.appendChild(packImg())
lg.Webpack.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.(png|svg|gif|jpe?g)$/,
// use: [
// {
// loader: 'file-loader',
// options: {
// esModule: false // 不转为 esModule,在js导入时无需写default取值
// }
// }
// ]
use: ['file-loader']
}
]
}
}
css-loader处理时,会默认将background-image: url('../img/02.react.png')
处理为require的形式,而require会返回一个ESModule,所以需要在Webpack配置中添加css-loader的属性值->esModule: false
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
img.css
.bgBox {
width: 240px;
height: 310px;
border: 1px solid #000;
background-image: url('../img/02.react.png');
}
Image.js
import oImgSrc from '../img/01.wb.png'
import '../css/img.css'
function packImg() {
// 01 创建一个容器元素
const oEle = document.createElement('div')
// 02 创建 img 标签,设置 src 属性
const oImg = document.createElement('img')
oImg.width = 600
// 写法1:使用require...default取值
// require导入默认一个对象,有一个default的键,代表的导入的内容
// oImg.src = require('../img/01.wb.png').default
// 写法2:lg.Webpack.js配置文件搭配使用,不需要写default取值
// esModule: false // 不转为 esModule
// oImg.src = require('../img/01.wb.png')
// 写法3:使用import导入,不需要写default或者config配置esModule
oImg.src = oImgSrc
oEle.appendChild(oImg)
// 03 设置背景图片
const oBgImg = document.createElement('div')
oBgImg.className = 'bgBox'
oEle.appendChild(oBgImg)
return oEle
}
document.body.appendChild(packImg())
lg.Webpack.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.(png|svg|gif|jpe?g)$/,
// use: [
// {
// loader: 'file-loader',
// options: {
// esModule: false // 不转为 esModule
// }
// }
// ]
use: ['file-loader']
}
]
}
}
修改file-loader的options用于设置图片名称和输出。
常见占位符:
[ext]: 扩展名
[name]: 文件名称
[hash]: 文件内容+MD4生成128为占位置,作为文件名
[contentHash]: 文件内容+MD4生成128为占位置,作为文件名
[hash:]: hash截取,作为文件名
[path]: 文件路径
lg.Webpack.js
{
test: /\.(png|svg|gif|jpe?g)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'img/[name].[hash:6].[ext]',
// outputPath: 'img'
}
}
]
}
其中,目录有两种写法,一种为添加outputPath: 'img'
,另一种为直接在name处写入img/
重新打包
url-loader
会将引入的文件进行编码,生成 DataURL
,相当于把文件翻译成了一串字符串,再把这个字符串打包到 JavaScript
。
一般来说,我们会发请求来获取图片或者字体文件。如果图片文件较多时(比如一些 icon
),会频繁发送请求来回请求多次,这是没有必要的。此时,我们可以考虑将这些较小的图片放在本地,然后使用 url-loader
将这些图片通过base64
的方式引入代码中。这样就节省了请求次数,从而提高页面性能。
安装 url-loader
npm install url-loader --save-dev
配置 webapck
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {},
},
],
},
],
},
};
引入一个文件,可以是 import
(或 require
)
import logo from '../assets/image/logo.png';
console.log('logo的值: ', logo); // 打印一下看看 logo 是什么
简单三步就搞定了。
见证奇迹的时刻
Webpack
执行Webpack
之后,dist
目录只生成了一个 bundle.js
。和 file-loader
不同的是,没有生成我们引入的那个图片。上文说过,url-loader
是将图片转换成一个 DataURL
,然后打包到JavaScript
代码中。
那我们就看看 bundle.js
是否有我们需要的 DataURL:
// bundle.js
(function(module, exports) {
module.exports = "data:image/jpeg;base64.........."; // 省略无数行
})
我们可以看到这个模块导出的是一个标准的 DataURL
。
一个标准的DataURL: data:[
通过这个 DataURL,我们就可以从本地加载这张图片了,也就不用将图片文件打包到 dist 目录下。
使用 base64 来加载图片也是有两面性的:
所以我们得有取舍,只对部分小 size
的图片进行 base64
编码,其它的大图片还是发请求吧。
url-loader
自然是已经做了这个事情,我们只要通过简单配置即可实现上述需求。
limit
的时候使用 fallback
的 loader
来处理文件loader
来处理大于limit
的文件,默认值是 file-loader
我们来试试设一个 limit
:
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
name: 'img/[name].[hash:6].[ext]',
limit: 25 * 1024 // 25kb
}
}
]
},
/**
* 01 url-loader base64 uri 文件当中,减少请求次数
* 02 file-loader 将资源拷贝至指定的目录,分开请求
* 03 url-loader 内部其实也可以调用 file-loader
* 04 limit
*/
重新执行 Webpack
,由于我们引入的 logo.png
大于 1000
,所以使用的是 file-loader
来处理这个文件。图片被打包到 dist 目录下,并且返回的值是它的地址:
(function(module, exports, __Webpack_require__) {
module.exports = __Webpack_require__.p + "dab1fd6b179f2dd87254d6e0f9f8efab.png";
}),
file-loader
的代码也不多,就直接复制过来通过注释讲解了:
import { getOptions } from 'loader-utils'; // loader 工具包
import validateOptions from 'schema-utils'; // schema 工具包
import mime from 'mime';
import normalizeFallback from './utils/normalizeFallback'; // fallback loader
import schema from './options.json'; // options schema
// 定义一个是否转换的函数
/*
*@method shouldTransform
*@param {Number|Boolean|String} limit 文件大小阈值
*@param {Number} size 文件实际大小
*@return {Boolean} 是否需要转换
*/
function shouldTransform(limit, size) {
if (typeof limit === 'boolean') {
return limit;
}
if (typeof limit === 'number' || typeof limit === 'string') {
return size <= parseInt(limit, 10);
}
return true;
}
export default function loader(src) {
// 获取 Webpack 配置里的 options
const options = getOptions(this) || {};
// 校验 options
validateOptions(schema, options, {
name: 'URL Loader',
baseDataPath: 'options',
});
// 判断是否要转换,如果要就进入,不要就往下走
// src 是一个 Buffer,所以可以通过 src.length 获取大小
if (shouldTransform(options.limit, src.length)) {
const file = this.resourcePath;
// 获取文件MIME类型,默认值是从文件取,比如 "image/jpeg"
const mimetype = options.mimetype || mime.getType(file);
// 如果 src 不是 Buffer,就变成 Buffer
if (typeof src === 'string') {
src = Buffer.from(src);
}
// 构造 DataURL 并导出
return `module.exports = ${JSON.stringify(
`data:${mimetype || ''};base64,${src.toString('base64')}`
)}`;
}
// 判断结果是不需要通过 url-loader 转换成 DataURL,则使用 fallback 的 loader
const {
loader: fallbackLoader,
options: fallbackOptions,
} = normalizeFallback(options.fallback, options);
// 引入 fallback loader
const fallback = require(fallbackLoader);
// fallback loader 执行环境
const fallbackLoaderContext = Object.assign({}, this, {
query: fallbackOptions,
});
// 执行 fallback loader 来处理 src
return fallback.call(fallbackLoaderContext, src);
}
// 默认情况下 Webpack 对文件进行 UTF8 编码,当 loader 需要处理二进制数据的时候,需要设置 raw 为 true
export const raw = true;
在 Webpack
出现之前,前端开发人员会使用grunt
和 gulp
等工具来处理资源,并
将它们从 /src
文件夹移动到 /dist
或/build
目录中。Webpack
最出色的功能之一就是,除了引入 JavaScript
,还可以内置的资源模块 Asset Modules
引入任何其他类型的文件。
在Webpack4
的时候以及之前,我们通常是使用file-loader
与url-loader
来帮助我们加载其他资源类型。
而Webpack5可以使用资源模块来帮助我们,称之为Asset Modules
,它允许我们打包其他资源类型,比如字体文件、图表文件、图片文件
等。
其中,资源模块类型我们称之为Asset Modules Type
,总共有四种,来代替loader
,分别是:
asset/resource:
发送一个单独的文件并导出URL,替代file-loader
asset/inline:
导出一个资源的data URI
,替代url-loader
asset/source:
导出资源的源代码,之前通过使用raw-loader实现asset:
介于asset/resource
和asset/inline
之间,在导出一个资源data URI
和发送一个单独的文件并导出URL之间做选择,之前通过url-loader+limit
属性实现。不过在介绍这四种资源模块类型之前,我们先说一下怎么自定义这些输出的资源模块的文件名
第一种方式,就是在Webpack
配置中设置 output.assetModuleFilename
来修改此模板字符串,其中assetModuleFilename默认会处理文件名后缀的点,所以无需手动添加点。此方式为公共的处理方法,当需要同时处理图片资源和字体资源时,通用方法会导致两种资源类型放在同一个目录下,此处不建议使用assetModuleFilename。
比如关于图片的输出文件名,我们可以让其都输出在images
文件夹下面,[contenthash]
表示文件名称,[ext]表示图片文件的后缀,比如.png、.jpg、.gif、jpeg等,[query]
表可能存在的参数
output: {
···
assetModuleFilename: 'images/[contenthash][ext][query]'
···
},
第二种方式,就是在module.rules
里面某种资源文件配置的时候,加上geneator
属性,例如
rules: [
{
test: /\.png/,
type: 'asset/resource',
generator: {
filename: 'images/[contenthash][ext][query]'
}
}
]
【注意】
generator
的优先级高于 assetModuleFilename
首先我们先新建一个文件夹来测试,文件夹目录如下,我们在src下面新建一个assets文件夹,里面放上事先准备好的集中不同类型的图片
index.js
import hello from './hello'
import img1 from './assets/man.jpeg'
import img2 from './assets/store.svg'
import img3 from './assets/women.jpg'
import Txt from './assets/wenzi.txt'
import dynamic from './assets/dongtu.gif'
hello()
const IMG1 = document.createElement('img')
IMG1.src = img1
document.body.appendChild(IMG1)
const IMG2 = document.createElement('img')
IMG2.src = img2
IMG2.style.cssText = 'width:200px;height:200px'
document.body.appendChild(IMG2)
const IMG3 = document.createElement('img')
IMG3.src = img3
document.body.appendChild(IMG3)
const TXT = document.createElement('div')
TXT.textContent = Txt
TXT.style.cssText = 'width:200px;height:200px;backGround:aliceblue'
document.body.appendChild(TXT)
const DYNAMIC = document.createElement('img')
DYNAMIC.src = dynamic
document.body.appendChild(DYNAMIC)
hello.js
function hello(){
console.log("hello-world!!!")
}
export default hello
index.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>你是,永远的神title>
head>
<body>
body>
html>
Webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-Webpack-plugin')
module.exports = {
entry : './src/index.js',
output : {
filename:'bundle.js',
path:path.resolve(__dirname,'./dist'),
clean:true,
//如果不设置,打包完之后资源会直接打包在dist目录下
assetModuleFilename:'images/[contenthash][ext][query]'
},
mode : 'development',
devtool:'inline-source-map',
plugins:[
new HtmlWebpackPlugin({
template:'./index.html',
filename:'app.html',
inject:"body"
})
],
devServer:{
static:'./dist'
},
module:{
rules:[{
test:/\.jpeg$/,
type:"asset/resource",
generator:{
filename:'images/[contenthash][ext][query]'
}
},{
test:/\.svg$/,
type:'asset/inline'
},{
test:/\.txt$/,
type:'asset/source'
},{
test:/\.(gif|jpg)$/,
type:'asset',
parser:{
dataUrlCondition:{
maxSize : 10 * 1024 * 1024
}
}
}]
}
}
asset/resource可以发送一个单独的文件并导出URL
我们将.jpeg后缀的图片设置type为asset/resource
,我们在index.js里面导入该图片并插入在body中,即将其当成资源显示在页面上
npx Webpack
打包之后,dist文件夹下的images文件中就出现该图片
asset/inline导出一个资源的data URI
仿照上面的方式,我们将.svg后缀的图片设置type为asset/inline,我们在index.js里面导入该图片并插入在body中,即将其当成资源显示在页面上,同时我们简单设置一下样式
不过不同的是,npx Webpack打包之后,dist文件夹下面并没有打包过.svg类型的图片
npx Webpack-dev-server --open
自动打开浏览器,我们在控制台中查看该图片类型,发现asset/inline类型确实可以导出Data URI形式的路径
source
资源,导出资源的源代码
仿照上面的方式,我们创建一个.txt后缀的文本文件,设置type为asset/source
,我们在index.js里面导入该文本并插入在body中,即将其当成资源显示在页面上,同时我们简单设置一下样式
不过不同的是,npx Webpack打包之后,dist文件夹下面并没有打包过.txt类型的文本文件
npx Webpack-dev-server --open
自动打开浏览器,我们在控制台中查看该文本类型,发现asset/source
类型确实可以导出资源的源代码
asset会介于asset/resource和asset/inline之间,在发送一个单独的文件并导出URL和 导出一个资源data URI之间做选择
默认情况下,Webpack5会以8k为界限来判断:
当资源大于8k时,自动按asset/resource
来判断
当资源小于8k时,自动按asset/inline
来判断
我们可以手动更改临界值,设置parser(解析),其是个对象,里面有个固定的属性,叫dataUrlCondition
,顾名思义,data转成url的条件,也就是转成bas64的条件,maxSize
是就相当于Limit了
module:{
rules:[
···
{
test:/\.(gif|jpg)$/,
type:'asset',
parser:{
dataUrlCondition:{
maxSize : 100 * 1024
}
}
}
···
]
}
这里我们设置100 * 1024即100kb,来作为临界值
【1b * 1024 = 1kb,1kb * 1024 = 1M】
仿照上面的方式,我们将.gif
和.jpg
后缀的图片设置type为asset资源类型,我们在index.js里面导入2张图片并插入在body中,即将其当成资源显示在页面上,其中.gif大小为128.11kb(超过了100kb的临界值),.jpg大小为12kb(未超过100kb的临界值)
npx Webpack打包之后,dist文件夹下面有打包过的.gif类型的图片,但是没有打包过.jpg类型的图片
npx Webpack-dev-server --open
自动打开浏览器,我们在控制台中查看2种图片类型,发现.gif图片是单独一个文件的URL路径,而.jpg图片是Data URI格式的base64路径
同上面所说,处理字体图标文件时,需要将其视为resource资源直接复制,所以需要使用asset/resource。此时准备好的字体文件及其目录如下:
在font目录中,准备了iconfont.css及其字体文件,其中iconfont.css中对font-family进行赋值对应的字体。
单独常见font.js文件,并在文件中引入iconfont.css以及自定义的index.css文件,创建页面DOM元素并显示。
Font.js
import '../font/iconfont.css'
import '../css/index.css'
function packFont() {
const oEle = document.createElement('div')
const oSpan = document.createElement('span')
oSpan.className = 'iconfont icon-linggan lg-icon'
oEle.appendChild(oSpan)
return oEle
}
document.body.appendChild(packFont())
当然,此时直接运行yarn build
肯定会报错,因为此时Webpack不认识ttf/woff/woff2
等资源,所以需要单独使用asset/resouce进行打包配置。
lg.Webpack.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
// assetModuleFilename: "img/[name].[hash:4][ext]"
},
module: {
rules: [
{
test: /\.(ttf|woff2?)$/,
type: 'asset/resource', // 使用资源复制
generator: {
filename: 'font/[name].[hash:3][ext]' // 指定字体文件输出路径
}
}
]
}
}
此时执行yarn build
,我们发现在dist目录下新增了font目录,font目录中的字体文件为Webpack拷贝而来。打开页面可以看到iconfont.css以及自定义的index.css文件样式已经生效。
插件机制是Webpack当中另外一个核心特性,它目的是为了增强Webpack项目自动化方面的能力,loader就是负责实现各种各样的资源模块的加载,从而实现整体项目打包,plugin则是用来去解决项目中除了资源以外,其它的一些自动化工作,例如:
总之,有了plugin的Webpack,几乎无所不能的实现了前端工程化当中绝大多数经常用到的部分,这也正是很多初学者会有Webpack就是前端工程化的这种理解的原因。
clean-Webpack-plugin
:自动清空dist目录
之前的测试中,每次都需要用户手动的删除dist目录,我们希望Webpack每次打包时,先将之前的dist目录删除,再进行打包,这里使用clean-Webpack-plugin
进行处理。
同样的,需要先进行安装clean-Webpack-plugin
yarn add clean-Webpack-plugin -D
之后按照其使用方法,在lg.Webpack.js
中进行插件配置。首先使用require导入clean-Webpack-plugin
,其中导出东西过多,需要进行解构:const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
。其次每个导出对象都是一个类,都有其自己的构造函数constructor,在plugins中使用时需要new CleanWebpackPlugin
。代码如下:
const path = require('path')
const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
// assetModuleFilename: "img/[name].[hash:4][ext]"
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.(png|svg|gif|jpe?g)$/,
type: 'asset',
generator: {
filename: "img/[name].[hash:4][ext]"
},
parser: {
dataUrlCondition: {
maxSize: 30 * 1024
}
}
},
{
test: /\.(ttf|woff2?)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name].[hash:3][ext]'
}
}
]
},
plugins: [
new CleanWebpackPlugin() // 每个插件就是一个类
]
}
先使用yarn build
进行打包,生成dist目录,随后在dist目录中手动添加一个a.txt文件,如果再次执行yarn build
后a.txt
被删除了,说明clean-Webpack-plugin
已经正常工作了。
除了清理 dist
的目录以外,还有一个非常常见的需求就是自动去生成使用打包结果的HTML,在这之前HTML都是通过硬编码的方式单独去存放在项目根目录下的。
index.html
每次打包完成之后手动需要修改title,以及打包产生的文件由于分包过后文件类型或者数量比较多,需要用户手动的进行修改,这些行为都可以通过html-Webpack-plugin
进行处理
默认情况下,不需要手动创建index.html
文件,Webpack在使用html-Webpack-plugin
插件后会默认在打包结果dist目录自动创建index.html文件。
首先手动删除准备好的index.html,没有使用html-Webpack-plugin
插件时,执行yarn build进行打包,通过观察发现dist
目录中并没有生成index.html
文件。
在配置文件中,首先导入html-Webpack-plugin。
const HtmlWebpackPlugin = require(‘html-Webpack-plugin’)
在plugins字段中进行使用:
const HtmlWebpackPlugin = require('html-Webpack-plugin')
...
plugins: [
new HtmlWebpackPlugin()
]
...
此时进行yarn build
打包处理,可以发现dist
目录中已经有了index.html文件。
此时index.html
内容是html-Webpack-plugin
默认提供的,可以在node_modules中找到html-Webpack-plugin
中的default_index.ejs
查看。
其中默认模板中的占位符在官方文档中有详细描述。
对于占位符,我们可以在plugin中进行传参,赋予其默认值。
new HtmlWebpackPlugin({
title: 'html-Webpack-plugin', // title占位符
})
const path = require('path')
const { DefinePlugin } = require('Webpack')
const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
const HtmlWebpackPlugin = require('html-Webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
// assetModuleFilename: "img/[name].[hash:4][ext]"
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.(png|svg|gif|jpe?g)$/,
type: 'asset',
generator: {
filename: "img/[name].[hash:4][ext]"
},
parser: {
dataUrlCondition: {
maxSize: 30 * 1024
}
}
},
{
test: /\.(ttf|woff2?)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name].[hash:3][ext]'
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'html-Webpack-plugin', // title占位符
})
]
}
再次yarn build
进行打包后,index.html
的title已经更新了。
此时我们使用的是html-Webpack-plugin
内置的html模板文件。但是在实际使用过程中,我们可能需要使用特殊的模板文件。此时使用template字段去定义自己的index.html模板。
此时使用yarn build
打包后,就会使用自定义的index.html
模板文件。
此时,网站图标的路径使用,再使用
DefinePlugin
(Webpack默认,无需安装)进行定义全局配置的常量。
new DefinePlugin({
BASE_URL: '"./"'
})
此时,完整的配置文件如下:
const path = require('path')
const { DefinePlugin } = require('Webpack')
const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
const HtmlWebpackPlugin = require('html-Webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
// assetModuleFilename: "img/[name].[hash:4][ext]"
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false
}
},
'postcss-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.(png|svg|gif|jpe?g)$/,
type: 'asset',
generator: {
filename: "img/[name].[hash:4][ext]"
},
parser: {
dataUrlCondition: {
maxSize: 30 * 1024
}
}
},
{
test: /\.(ttf|woff2?)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name].[hash:3][ext]'
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'html-Webpack-plugin',
template: './public/index.html'
}),
new DefinePlugin({
BASE_URL: '"./"' // Webpack会将常量原封不动的拿走,所以需要使用引号包裹
})
]
}
再次进行打包后,结果如下:
除了自定义输出文件的内容,同时输出多个页面文件也是一个非常常见的需求。其实配置非常简单,配置文件中添加一个新的HtmlWebpackPlugin对象,配置如下:
plugins: [
new CleanWebpackPlugin(),
// 用于生成 index.html
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
meta: {
viewport: 'width=device-width'
},
template: './src/index.html'
}),
// 用于生成 about.html
new HtmlWebpackPlugin({
filename: 'about.html', // 用于指定生成的文件名称,默认值是index.html
title: 'About html'
})
]
在项目中,一般还有一些不需要参与构建的静态文件,它们最终也需要发布到线上,例如我们网站的favicon.icon
,一般会把这一类的文件统一放在项目的public
目录当中,希望Webpack
在打包时,可以一并将它们复制到输出目录。
对于这种需求,可以借助于copy-Webpack-plugin
,先安装一下这个插件,然后再去导入这个插件的类型,最后同样在这个plugin属性当中去添加一个这个类型的实例,这类型的构造函数它要求传入一个数组,用于去指定需要去拷贝的文件路径,它可以是一个通配符,也可以是一个目录或者是文件的相对路径,这里使用plugin,它表示在打包时会将所有的文件全部拷贝到输出目录,再次运行Webpack指令,打包完成过后,public目录下所有的文件就会同时拷贝到输出目录。
const path = require('path')
const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
const HtmlWebpackPlugin = require('html-Webpack-plugin')
const CopyWebpackPlugin = require('copy-Webpack-plugin')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
// publicPath: 'dist/'
},
module: {
rules: [
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.png$/,
use: {
loader: 'url-loader',
options: {
limit: 10 * 1024 // 10 KB
}
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
// 用于生成 index.html
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
meta: {
viewport: 'width=device-width'
},
template: './src/index.html'
}),
// 用于生成 about.html
new HtmlWebpackPlugin({
filename: 'about.html'
}),
new CopyWebpackPlugin({
patterns: [
{
from: 'public',
globOptions: {
ignore: ['**/index.html'] // 必须写入**/, ** 两个星号的意思是在当前路径
}
}
]
})
]
}
由于Webpack
默认就能处理代码当中的import
和export
,所以很自然都会有人认为Webpack会自动编译的ES6代码。实则不然,那是Webpack的仅仅是对模块去完成打包工作,所以说它才会对代码当中的import和export做一些相应的转换,它并不能去转换我们代码当中其它的ES6特性。
如果需要将ES6的代码打包并编译为ES5的代码,需要一些其它的编译形加载器。这里安装一些额外的插件。
首先,Webpack是可以识别ES6+的语法的,这里来测试一下,在index.js中写入ES6+的语法,使用yarn build进行打包,观察打包过后的代码可以发现,Webpack原封不动的把index.js
中的ES6+
语法代码拿了过来,并没有进行任何处理。
所以针对ES6+语法,需要使用特殊工具进行处理,这里安装@babel/core
以及命令行工具@babel/cli
进行代码测试,看babel默认是否会帮助处理ES6+语法。
yarn add @babel/core @babel/cli
yarn babel
使用后发现,babel仍然没有帮我们处理ES6+语法,这是为什么呢?原因是babel还需要使用特殊插件进行处理。
yarn babel
目标路径 --out-put
输出路径
yarn babel src --out-put build
因此,我们需要特殊的插件来对箭头函数或者const、let关键字进行处理。
@babel/plugin-transform-arrow-functions
(处理箭头函数)@babel/plugin-transform-block-scoping
(处理块级作用域)yarn add @babel/plugin-transform-arrow-functions @babel/plugin-transform-block-scoping
# 执行babel
yarn babel src --out-dir build --plugins=@babel/plugin-transform-arrow-functions,@babel/plugin-transform-block-scoping
重新执行后,发现箭头函数和let
、const
关键字作用域已经被处理成var
关键字。
但是我们发现,每次需要处理不同的特殊情况,都需要安装不同的babel插件,特别不方便。因此babel将绝大多数有关ES6+语法以及stage草案的插件组合成一个集合@babel/preset-env,
以后只需要使用这一个集合就可以处理绝大多数的ES6+
语法。
# 安装@babel/preset-env
yarn add @babel/preset-env
# 使用babel进行编译
yarn babel src --out-dir build --presets=@babel/preset-env