webpack 5 combat (1)

1. Why use webpack

Personally divide front-end development into three stages:

1.1 Web1.0

Web1.0 front-end main work:

  • The front end mainly writes static pages
  • For the use of JavaScript, it is mainly for form verification and animation effect production

1.2 AJAX of Web2.0

With the birth of AJAX, the working mode of the front-end has also undergone great changes. The front-end not only displays the interface, but also manages data and interacts with users. During such a stage, excellent front-end tool libraries like jQuery were born.

1.3 Big front-end development (modern web development)

At this stage, the front-end work has become more diverse and complicated. For example, the front-end not only needs to develop the PC-side web interface, but also the mobile-side web interface, as well as small programs and official accounts, and sometimes even needs to develop apps. and desktop clients.

As more and more things need to be done, the process becomes more and more complex, so some problems will arise. For example:

Modern Web Development "Problems"

  • Adopt modular development

    • Different browsers have different support for modularization, and there are multiple implementation specifications for modularization itself, which affects the final output
  • Use new features to improve efficiency and ensure safety

    • During the coding process, in order to improve development efficiency, ES6+, TypeScript, Saas, and Less will also be used. Browsers cannot handle these conditions normally by default.
  • Monitor the development process in real time using hot updates

  • Project result packaging and compression optimization

There needs to be a tool to stand out to solve the problem, allowing developers to do whatever they want at the entrance, and complete the development with their favorite technology stack, so that there is no need for a relationship process, but the final result can be displayed normally on the browser, so here will be Use the packaging tool. Currently Vue, React, and Angular integrate Webpack themselves.

Second, good at Webpack

Webpack Definition: Provides static module packaging for modern JavaScript applications

Webpack features:

  • Packaging: Package different types of resources according to module processing. js, css, img and other resources can be processed in the form of modules, and then uniformly packaged and output
  • Static: Static resources are finally produced after packaging
  • Module: Webpack supports modular development of different specifications (ES Module, CommonJS, AMD, etc.)

Components are shown in the directory structure and coded

insert image description here
Previewed in the web server and found that although it is used in index.html type="module", it still cannot recognize ES Module and CommonJS at the same time.

insert image description here
At this time, the pre-installed Webpack will work. Enter: Webpack in the command line terminal, and then find that the dist directory is output in the directory. It should be noted here that Webpack packaging will find the src directory under the project directory by default, and find index.js as the entry file, package the dependencies, and output them to the dist directory. The output result is main.js by default. as follows:

insert image description here

(() => {
    
     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()) })() })();

Observe main.js and find that the current Webpack does not solve the syntax compatibility problem of ES6+

At this point, change the js file introduced in index.html to dist/main.js.

<body>
  <script src="./dist/main.js"></script>
</body>

Three, webpack configuration file

  • Packaging via command line arguments
    • yarn Webpack --entry ./src/main.js --output-path ./build, where --entrythe entry file is specified and --output-paththe input path is specified
  • Configure short commands via package.json
...
"scripts": {
    
    
  "build": "Webpack --entry ./src/main.js --output-path ./build"
}
...

yarn buildPackaging via command line run

  • Configure through the webpack.config.js configuration file
const path = require('path')
module.exports = {
    
    
  entry: './src/index.js',
  output: {
    
    
    filename: 'build.js',
    path: path.resolve(__dirname, 'dist')  // 必须使用绝对路径,不然会Webpack抛出错误
  }
}

4. Webpack dependency graph

insert image description here

Directory structure:
insert image description here
import lg.js in index.js, and then import main.js under the dist directory in index.html. During the packaging process, Webpack will automatically find and import dependencies, and finally package them as main.js.

As mentioned above, you can use command line parameters to configure the entry file, output directory, and output file name. Here, you can use the –config parameter to customize the Webpack configuration file. For example:

...
{
    
    
"scripts" : {
    
    
"build": "Webpack --config my-Webpack-config.js"
}
}
...

At this point, Webpack will read the configuration from my-Webpack-config.js for packaging.

5. CSS-Loader

The script in index.html introduces the packaged main.js

Introduce login.js in the index.js entry file

// login.js
function createDom() {
    
    
  const h2 = document.createElement('h2')
  h2.innerHTML = '拉勾教育'
  h2.className = 'title'
  return h2
}
document.body.appendChild(createDom())

At this point, you need to add css style to .title

/*
login.css
*/
.title {
    
    
  color: red
}

Introduce login.css in login.js and pack it with Webpack. At this time, an exception will be thrown, and the css file is not a module.

import '../css/login.css'

function login() {
    
    
  const oH2 = document.createElement('h2')
  oH2.innerHTML = '拉勾教育前端'
  oH2.className = 'title'
  return oH2
}

document.body.appendChild(login())


what is loader

Loader is a module that uses js internally to implement specific logic. For example, a loader is needed to convert the login.css code into a module that Webpack can recognize.

css-loader

Install css-loader

yarn add css-loader

The use of loader in webpack4 is generally divided into three types:

  • inline loader
  • Configuration file loader
  • Use loader on the Webpack-cli command line

In Webpack5, it is not recommended to use the loader in the cli, and it has been discarded

Use loader in the row, multiple loaders are separated by English **!**

import 'css-loader!../css/login.css'


function login() {
    
    
  const oH2 = document.createElement('h2')
  oH2.innerHTML = '拉勾教育前端'
  oH2.className = 'title'
  return oH2
}

document.body.appendChild(login())

Re-execute yarn webpack, although there is no syntax error, the style does not take effect. One more is required style-loader.

configuration file usingcss-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']
      }
    ]
  }
}

6. Use of style-loader

Then go back to webpack.config.js and point the path of the entry file to the newly created css file. Then configure the loader component, the test value is a regular expression /.css$/, use configures an array, respectively style-loaderandstyle-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'
        ]
      }
    ]
  }
}


Start the command line, yarn Webpack, run it through serve ., and visit it in the browser to see that our css has taken effect.

ps : In use, if multiple loaders are configured, the execution order is from the last element of the array to the front. So here we must put the css-loader at the end, because we must first convert the css code into a module through the css-loader before it can be packaged normally.

The working code of style-loader is in bundle.js, part of the code is as follows:
insert image description here
loader is the core of webpack to realize the modularization of the entire front end, and any type of resource can be loaded through different loaders.

Seven, less-loader

Use less to write css code in the project. First, import login.less in login.js normally, and use Webpack to compile normally. It is found that the error report is basically the same as that without css-loader. Webpack does not support the compilation of less files by default, so according to the idea, you need to compile less files into css files first, and then use css-loader and style-loader together to import css styles into index.html. Try it out below:

First install less and try to compile login.less into index.css

npm i less -D  # 安装less
npx less ./src/css/login.less index.css  # 使用less将login.less编译为index.css

insert image description here
Secondly, introduce it in login.js:

// 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())

At this time, run Webpack for packaging, and find that an error will be thrown. The error type is the same as when Webpack cannot compile the css file mentioned above.

Going back to the initial idea, we need less-loader to compile less files into css files, and then use css-loader with style-loader to compile css styles into html files, so configuration is required, and the configuration idea is the same as css. The following code:

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: Remember that the loader loading order is: from right to left , from bottom to top

insert image description here

8. .browserslistrc (Help compatibility with different platforms) Workflow - Handling Compatibility

https://caniuse.com/

.browserslistrc is a configuration file for sharing target browsers and node versions between different front-end tools. It is mainly used by the following tools:

Autoprefixer
Babel
post-preset-env
eslint-plugin-compat
stylelint-unsupported-browser-features
postcss-normalize

Webpack will install browserlistrc by default

Front-end engineering needs to be configured in package.json

{
    
    
  "browserslist": [
    "last 1 version",
    "> 1%",
    "maintained node versions",
    "not dead"
  ]
}

Can also be configured in the .browserslistrc file

# 注释是这样写的,以#号开头
last 1 version #最后的一个版本
> 1%  #代表全球超过1%使用的浏览器
maintained node versions #所有还被 node 基金会维护的 node 版本
not dead

If not configured, the default is: > 0.5%, last 2 versions, Firefox ESR, not dead
Query the target browser in the current directorynpx browserslist

List of query conditions
You can use the following query conditions to limit the version range of browsers and node (case-insensitive):

> 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在任和查询条件前面,表示取反

Note:
1. You can use the following writing method to import configuration data from another package that outputs browserslist configuration:

"browserslist": [
	"extends browserslist-config-mycompany"
]

For security reasons, additional configuration packages only support package names prefixed with browserslist-config-. npm package scope also supports @scope/browserslist-config, for example:
@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. Differentiated configuration of environments
You can configure different browser query conditions for different environments. Browserslist will rely on BROWSERSLIST_ENV or NODE_ENV to query browser version ranges. If neither of the two environment variables is configured with the correct query conditions, the query conditions will be loaded from the configuration item corresponding to production first, and if not, the default configuration will be applied.
In package.json:

  "browserslist": {
    "production": [
      "> 1%",
      "ie 10"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version"
    ]
  }

.broswerslistrcin the configuration file

[production staging]
> 1%
ie 10

[development]
last 1 chrome version
last 1 firefox version

Nine, postcss workflow

The official website says: "PostCSS, a framework for processing CSS using JavaScript". This sentence highly summarizes the role of PostCSS, but it is too abstract. As far as I understand, PostCSS mainly does three things:

  1. parse: The string of the CSS file is parsed into the framework of the Abstract Syntax Tree (Abstract Syntax Tree). During the parsing process, it will check whether the CSS syntax is correct. If it is not correct, an error message will be given.
  2. runPlugin:Execute the plugin function. PostCSS itself does not handle any specific tasks, it provides events named after specific properties or rules. Plugins with specific functions (such as autoprefixer, CSS Modules) will register event listeners. PostCSS will rescan the AST at this stage and execute the registered listener functions.
  3. generate: After the plugin processes the AST, PostCSS converts the processed AST object into a CSS string.

insert image description here
"If there is no plug-in", then the initial CSS string passed in is the same as the CSS string generated by generate. It can be seen that PostCSS itself does not handle any specific tasks, and it is only practical when we attach various plug-ins to it.

The first stage: parse

A Brief Introduction to CSS Syntax

A CSS rule-set consists of selectors and declaration blocks:

insert image description here

  • The selector points to the HTML element that you need to style.
  • A declaration block contains one or more declarations separated by semicolons.
  • Each declaration consists of a CSS property name and a value, separated by colons.
  • Multiple CSS declarations are separated by semicolons, and blocks of declarations are enclosed in curly braces.

five types of objects

The AST describes CSS syntax with five classes of objects. Here is a specific example, and then print out the corresponding AST results to understand the correspondence between the five types of AST objects and CSS syntax.

Write the following in the app.css file:

@import url('./app-02.css');

.container {
    
    
  color: red;
}

Declaration object

Declaration object is used to describe each declaration statement in CSS.

  • type marks the type of the current object
  • parent records the instance of the parent object
  • prop records the property name in the declaration
  • value the value in the record declaration
  • The raws field records the string before the declaration, the string of symbols between the declaration attribute and the value
  • The rest of the fields are explained in the comments in the code.

The above CSS file color: red;will be described as the following object:

{
    
    
    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 字段记录声明的值
}

The meaning and function of each field has been explained in the form of comments.

Rule object

Rule objects describe selectors.

  • type the type of record object
  • parent records the instance of the parent object
  • nodes records instances of sub-objects
  • selector the record selector string
  • raws records the string before the selector, the string between the selector and the brace, the string between the last declaration and the closing brace
  • The rest of the fields are explained in the comments in the code.

The object of .container in the above app.css file after postcss translation is (the meaning and function of each field have been explained in the form of comments):

{
    
    
    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 object

The Root object is the root object of the AST object.

type records the type of the current object,
and the nodes attribute records the instance of the object corresponding to the child node.
The root object in the above app.css file is (the meaning and function of each field has been explained in the form of comments):

{
    
    
    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 object

In addition to selectors in CSS, there is also a type of syntax that @starts with , for example @import, , @keyframes, @font-faceand PostCSS parses this type of syntax into AtRule objects.

  • type records the type of the current object
  • parent records the parent object of the current object
  • name records the word that follows @
  • params record name value

For example @import url("./app-02.css");will be parsed into the following object:

{
    
    
    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 object

Comments in css files are parsed into Comment objects. The text field records the comment content. /* 你好 */is parsed into:

{
    
    
    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
}

Illustrate the inheritance relationship between five types of objects

As you can see from the previous paragraph, CSS is parsed into Declaration, Rule, Root, AtRule, and Comment objects. These objects have many public methods. PostCSS uses the object-oriented inheritance idea to extract public methods and public properties into the parent class.

Root, Rule, and AtRule can all have child nodes, and all have the nodes attribute. The three of them inherit from the Container class, and the operation methods for nodes are all written in the Container class. Container, Declaration, and Comment are inherited from the Node class. All objects have Symbol('isClean'), Symbol('my'), raws, source, type attributes, and methods such as toString() and error(). These attributes and Methods are defined in the Node class.

Container and Node are used to extract public properties and methods, and will not generate their instances.

The inheritance relationship between the five classes is shown in the figure below:
insert image description here
there is no method to exhaustively enumerate classes in the figure, and curious students can directly look at the source code file: https://github.com/postcss/postcss/tree/main/lib .

A specific algorithm for parsing CSS syntax into AST objects

The position in the source code corresponding to the algorithm is: the method postcss/lib/parser.jsin , the amount of code is not large, you can check it yourself.parse

The second stage: runPlugin

PostCSS itself does not handle any specific tasks, and it becomes useful only when we attach various plugins to it.

After PostCSS parses the CSS string into an AST object, it will scan one side of the AST object, and each type of AST object can have a corresponding listener. When traversing to a certain type of object, if there is a listener for the object, its listener will be executed.

First class listener

The event listeners "named after specific properties or rules" provided by PostCSS are as follows:

CHILDREAN Represents event listeners for child nodes.

// 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 traverses the AST tree in a depth-first manner.

  • Traversing to the Root root object, the first step will execute all the Root event listeners registered by the plug-in, the second step is to check whether the Root has sub-objects, if so, traverse the sub-objects, and execute the event listeners corresponding to the sub-objects; if there are no sub-objects object, go directly to the third step, and the third step will execute the RootExit event listeners registered by all plug-ins. The listeners of the Root and RootExit events registered by the plug-in can only be functions. The first parameter of the function is the Root object of the currently accessed AST, and the second parameter is the Result object of postcss and some other attributes. Through the Result object, information such as css string and opts can be obtained.
{
    
    
  Root: (rootNode, helps) => {
    
    },
  RootExit: (rootNode, helps) => {
    
    }
}

  • Traversing to the Rule object is the same logic as accessing the Root root object. First execute the Rule event listeners registered by all plug-ins, then traverse the child objects, and finally execute the RuleExit event listeners registered by all plug-ins. The listeners of the Rule and RuleExit events registered by the plug-in can only be functions.
{
    
    
  Rule: (ruleNode, helps) => {
    
    },
  RuleExit: (ruleNode, helps) => {
    
    }
}

  • Traverse to the AtRule object. The event listener of the AtRule registered by the plug-in can be a function or an object. Object type listener, the key of the object property is the name value of the AtRule object, and the value is the function. AtRuleExit is the same logic. The execution sequence of events is: ['AtRule', 'AtRule-import', CHILDREN, 'AtRuleExit', 'AtRuleExit-import']. CHILDREAN represents events for child nodes. ```// function { AtRule: (atRuleNode, helps) => {} }
// 对象
{
    
    
  AtRule: {
    
    
      import: (atRuleNode, helps) => {
    
    },
      keyframes: (atRuleNode, helps) => {
    
    }
  }
}

  • Traverse to the Declaration object. The event listener of the Declaration registered by the plug-in can be a function or an object. The key of the object property is the prop value of the Declaration object, and the value is a function. DeclarationExitExit is the same logic. The execution sequence of events is: ['Declaration', 'Declaration-color', 'DeclarationExit', 'DeclarationExit-color']. Declaration has no sub-objects, and only needs to execute the events of the current object, and does not need to execute the events of sub-objects in depth.
// 函数
{
    
    
  Declaration: (declarationNode, helps) => {
    
    }
}

// 对象
{
    
    
  Declaration: {
    
    
      color: (declarationNode, helps) => {
    
    },
      border: (declarationNode, helps) => {
    
    }
  }
}

  • Traverse to the Comment object. Execute the Comment event listeners registered by all plug-ins in turn, and then execute the CommentExit event listeners registered by all plug-ins.

The second type of listener

In addition to event listeners named after specific properties or rules, PostCSS has the following four:

{
    
    
  postcssPlugin: string,
  prepare: (result) => {
    
    },
  Once: (root, helps) => {
    
    },
  OnceExit: (root, helps) => {
    
    },
}

The overall implementation of PostCSS plugin events is: [prepare, Once, ...一类事件,OnceExit], postcssPlugin is the plugin name, not the event listener.

  • postcssPlugin: string type, the name of the plug-in, when the plug-in is executed, an error is reported, and the user is prompted which plug-in reported the error.
  • prepare: function type, prepare is executed first, before all events are executed, and is used when multiple listeners of the plug-in share data. The input parameter of prepare is the Result object, and the return value is the listener object. Through the Result object, information such as css string and opts can be obtained.
{
    
    
  postcssPlugin: "PLUGIN NAME",
  prepare(result) {
    
    
    const variables = {
    
    };
    return {
    
    
      Declaration(node) {
    
    
        if (node.variable) {
    
    
          variables[node.prop] = node.value;
        }
      },
      OnceExit() {
    
    
        console.log(variables);
      },
    };
  },
};

  • Once: function type, executed after prepare and before a class of events, once will only be executed once.
{
    
    
   Once: (root, helps) => {
    
    }
}

Screenshot of plugin source code

At this time, look at the popular postcss-based tools on the market. Is there any enlightenment?

  • autoprefix
  • postcss-import-parser
  • postcss-modules
  • postcss-modules

There are many postcss-based plugins, which can be found at: https://github.com/postcss/postcss/blob/main/docs/plugins.md

The third stage: generate

The generate process still traverses the AST object in a depth-first manner, and stitches strings for different instance objects. The location in the corresponding source code of the algorithm is: postcss/lib/stringifier.jsthe stringify method in , the amount of code is not large, you can check it yourself.

10. postcss-loader handles css compatibility

css3 automatically prefixes -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'
        ]
      }
    ]
  }
}

Handle the octal of the color

color: #12345678 The last two digits are used to specify the transparency

postcss-preset-envPresets are a collection of plugins that postcss-preset-envare already included autoprefixer, so you can just usepostcss-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

Configure the postcss plugin using the configuration file alone

postcss.config.js

module.exports = {
    
    
  plugins: [
    require('postcss-preset-env')
  ]
}

Eleven, importLoaders attribute

Problem:
The content of test.css is as follows:

.title {
    
    
    transition: all .5s;
    user-select: none;
}

The content of login.css is as follows:

/* import test.css */

@import './test.css';
.title {
    
    
    color: #12345678;
}

Again, npm run build found that the code in test.css after running has not been processed for compatibility.

problem analysis:

  • The login.css @import statement imports test.css
  • login.css can be matched, and when it is matched, postcss-loader will work
  • Based on the current code, postcss-loader gets the code in login.css and analyzes it based on our filter conditions without any additional processing
  • Finally, the code is handed over to css-loader
  • At this time, css-loader can handle @import media, url..., at this time it got the test.css file again, but the loader will not look back
  • Finally, hand over the processed css code to style-loader for display

Solve the problem: modify Webpack.config.js to set some properties for 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']
            }
        ]
    }
}

insert image description here

Run successfully again. The running results are as follows, and the content of test.css has also been modified successfully.

12. File-loader processes pictures

1 JS imports images and writes them into HTML

Import the img image into the js file and output it to the page

To process pictures in jpg, png and other formats, we also need a corresponding loader: file-loader. The role of file-loader is to help us process a file resource introduced by import/require(), etc., and put it in our output folder; of course, you can also modify its name and folder

Installfile-loader

npm install file-loader -D

Import images in Image.js and display them on the page:

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']
      }
    ]
  }
}

2 JS imports pictures and sets them to css style

When css-loader is processed, it will be background-image: url('../img/02.react.png')processed in the form of require by default, and require will return an ESModule, so you need to add the attribute value of css-loader in the Webpack configuration ->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']
      }
    ]
  }
}

Thirteen, set the picture name and output

Modify the options of file-loader to set the image name and output.

Common placeholders:

[ext]: 扩展名 
[name]: 文件名称 
[hash]: 文件内容+MD4生成128为占位置,作为文件名 
[contentHash]: 文件内容+MD4生成128为占位置,作为文件名 
[hash:<length>]: hash截取,作为文件名
[path]: 文件路径

lg.Webpack.js

{
    
    
  test: /\.(png|svg|gif|jpe?g)$/,
  use: [
    {
    
    
      loader: 'file-loader',
      options: {
    
    
        name: 'img/[name].[hash:6].[ext]',
        // outputPath: 'img'
      }
    }
  ]
}

Among them, there are two ways to write the directory, one is to add outputPath: 'img', and the other is to write directly at the name and img/repackage

14. url-loader processes pictures

what is url-loader

url-loaderThe imported files will be encoded and generated DataURL, which is equivalent to translating the files into a string of strings, and then packing the strings into the JavaScript.

when to use

Generally, we will send a request to get an image or font file. If there are many image files (such as some icon), frequent requests will be sent back and forth multiple times, which is unnecessary. At this point, we can consider putting these smaller images locally, and then url-loader use base64the method of passing these images into the code. This saves the number of requests and improves page performance.

how to use

install url-loader

npm install url-loader --save-dev

configure webapck

module.exports = {
    
    
  module: {
    
    
    rules: [
      {
    
    
        test: /\.(png|jpg|gif)$/,
        use: [
          {
    
    
            loader: 'url-loader',
            options: {
    
    },
          },
        ],
      },
    ],
  },
};

Introduce a file, either import(or require)

import logo from '../assets/image/logo.png';
console.log('logo的值: ', logo); // 打印一下看看 logo 是什么

Just three simple steps.

Witness the moment of miracle

Webpack

After execution Webpack, dist only one directory is generated bundle.js. file-loader The difference is that the picture we introduced is not generated. As mentioned above, url-loader it is to convert the picture into one DataURLand then pack it into JavaScriptthe code.

then we'll see bundle.jsif there's anything we need DataURL:

// bundle.js
(function(module, exports) {
    
    
module.exports = "data:image/jpeg;base64.........."; // 省略无数行
})

We can see that this module exports a standard DataURL.

A standard DataURL:data:[<mediatype>][;base64],<data>

Through this DataURL, we can load this picture from the local, so there is no need to pack the picture file into the dist directory.

Using base64 to load images also has two sides:

  • Advantages: Save requests and improve page performance
  • Disadvantages: increase the size of local files, reduce loading performance

So we have to make a trade-off, only encode some size small pictures base64 , and send requests for other large pictures.

url-loaderNaturally, this has already been done, and we can achieve the above requirements through simple configuration.

options

  • limit: file threshold, when the file size is greater limit than the used tofallback process the fileloader
  • fallback: specify a loader to handle limitfiles larger thanfile-loader

Let's try to set up one 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
 */

Re-execute Webpack, since we introduced logo.pngis greater than 1000, so we use file-loaderto process this file. The image is packed into the dist directory, and the returned value is its address:

(function(module, exports, __Webpack_require__) {
    
    
module.exports = __Webpack_require__.p + "dab1fd6b179f2dd87254d6e0f9f8efab.png";
}),

Source code analysis

file-loader There are not many codes, so I just copied it and explained it through comments:

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;

Fifteen, asset processing pictures

WebpackBefore , front-end developers would use tools likegrunt and to process resources and move them from the folder into the or directory. One of the coolest features is that, in addition to importing , the built-in resource module can also import any other type of file.gulp
/src /dist /buildWebpack JavaScriptAsset Modules

Before Webpack4and after, we usually use file-loaderand url-loaderto help us load other resource types.

1. Four types of Asset Modules Type

And Webpack5 can help us using resource modules, called it Asset Modules, which allows us to package other resource types, such as 字体文件、图表文件、图片文件etc.

Among them, we call the resource module type Asset Modules Type, and there are four types in total to replace loader, namely:

  1. asset/resource:Send a separate file and export URL, instead offile-loader
  2. asset/inline:export a resource data URI, instead ofurl-loader
  3. asset/source:Export the source code of the resource, previously achieved by using raw-loader
  4. asset:Between asset/resourceand asset/inlineBetween, choose between exporting a resource data URIand sending a separate file and exporting the URL, previously url-loader+limitachieved via a property.

But before introducing these four types of resource modules, let's talk about how to customize the file names of these output resource modules

2 Custom resource module name

2.1 assetModuleFilename

The first way is to modify the template string byWebpack setting in the configuration , where assetModuleFilename will process the dots of the file name suffix by default, so there is no need to add dots manually. output.assetModuleFilenameThis method is a public processing method. When image resources and font resources need to be processed at the same time, the general method will cause the two resource types to be placed in the same directory. It is not recommended to use assetModuleFilename here.

For example, regarding the output file name of the picture, we can let it be output under imagesthe folder, [contenthash]indicating the file name, and [ext] indicates the suffix of the image file, such as .png, .jpg, .gif, jpeg, etc., and the [query]possible parameters

output: {
    
    
   ···
   assetModuleFilename: 'images/[contenthash][ext][query]' 
   ···
},

2.2 generator attribute

The second way is to add attributes module.ruleswhen configuring certain resource files inside, such asgeneator

rules: [
	{
    
     
		test: /\.png/, 
		type: 'asset/resource', 
		generator: {
    
     
	      	filename: 'images/[contenthash][ext][query]' 
	 	} 
	}
]

[Note]
generator has higher priority thanassetModuleFilename

3 Four types of imports

First, let’s create a new folder to test. The folder directory is as follows. We create a new assets folder under src, and put the different types of images index.js prepared in advance in it
insert image description here
.

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
                }
            }
        }]
    }
    
}

3.1 resource resource type

asset/resource can send a separate file and export URL

We set the type of the picture with the .jpeg suffix asset/resource, we import the picture in index.js and insert it into the body, that is, display it on the page as a resource

npx WebpackAfter packaging, the image will appear in the images file under the dist folder

3.2 inline resource type

asset/inline exports the data URI of a resource

Following the above method, we set the type of the picture with the .svg suffix to asset/inline, we import the picture in index.js and insert it into the body, that is, it will be displayed on the page as a resource, and we simply set the style

However, the difference is that after npx Webpack is packaged, no .svg type images are packaged under the dist folder

npx Webpack-dev-server --openAutomatically open the browser, we check the image type in the console, and found that the asset/inline type can indeed export the path in the form of Data URI

3.3 source resource type

sourceresources, export the source code of the resource

Following the above method, we create a text file with a .txt suffix, set the type to asset/source, we import the text in index.js and insert it into the body, that is, it will be displayed on the page as a resource, and we simply set the style

But the difference is that after npx Webpack is packaged, no .txt type text files are packaged under the dist folder

npx Webpack-dev-server --openOpen the browser automatically, we check the text type in the console and find that asset/sourcethe type can indeed export the source code of the resource

3.4 Asset general resource type

Asset will be between asset/resource and asset/inline, choose between sending a separate file and exporting URL and exporting a resource data URI

By default, Webpack5 will use 8k as the limit to judge:

When the resource is greater than 8k, automatically press asset/resourceto judge
When the resource is less than 8k, automatically press to asset/inlinejudge
We can manually change the threshold and set parser (analysis), which is an object with a fixed attribute called, as the name suggests, data dataUrlConditiontransfer The condition for turning into url, that is, the condition for turning into bas64, maxSizeis equivalent to Limit

module:{
    
    
        rules:[
        ···
        {
    
    
            test:/\.(gif|jpg)$/,
            type:'asset',
            parser:{
    
    
                dataUrlCondition:{
    
    
                    maxSize : 100 * 1024 
                }
            }
        }
        ···
        ]
    }

Here we set 100 * 1024 or 100kb as the critical value
【1b * 1024 = 1kb,1kb * 1024 = 1M】

Following the above method, we set the type of the picture .gifwith .jpgthe suffix as asset resource type, we import 2 pictures in index.js and insert them into the body, that is, they are displayed on the page as resources, and the size of .gif is 128.11kb (over the 100kb threshold), .jpg size is 12kb (not over the 100kb threshold)

After npx Webpack is packaged, there are packaged .gif type pictures under the dist folder, but no packaged .jpg type pictures

npx Webpack-dev-server --openAutomatically open the browser, we check the two types of pictures in the console, and found that the .gif picture is the URL path of a single file, and the .jpg picture is the base64 path of the Data URI format

Sixteen, asset processing icon font

As mentioned above, when dealing with font icon files, they need to be directly copied as resource resources, so you need to use asset/resource. The prepared font files and their directories are as follows:

In the font directory, iconfont.css and its font files are prepared, wherein iconfont.css assigns fonts corresponding to font-family.

Common font.js file separately, and introduce iconfont.css and custom index.css file in the file, create page DOM elements and display them.

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())

Of course, if you run it directly at this time, yarn buildyou will definitely report an error, because Webpack does not recognize ttf/woff/woff2other resources at this time, so you need to use asset/resouce separately for packaging configuration.

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]'  // 指定字体文件输出路径
        }
      }
    ]
  }
}


Execution at this time yarn build, we found that the font directory has been added under the dist directory, and the font files in the font directory are copied by Webpack. Open the page and you can see that the iconfont.css and custom index.css file styles have taken effect.

Seventeen, Webpack plug-in use

Plug-in mechanism is another core feature of Webpack, its purpose is to enhance the ability of Webpack project automation, loader is responsible for loading various resource modules, so as to realize the overall project packaging, plugin is used Come and go to solve other automation tasks in addition to resources in the project, such as:

  • The plugin can help us to automatically clear the dist directory before packaging, which is the result of the last packaging;
  • Or it can be used to help us copy those resource files that do not need to participate in packaging to the output directory;
  • Or it can be used to help us compress the code that we package the result output‌.

In short, with the plugin's Webpack, it is almost omnipotent to realize most of the frequently used parts in front-end engineering, which is why many beginners have the understanding that Webpack is front-end engineering. ‌.

clean-Webpack-plugin: Automatically clear the dist directory

In the previous test, the user had to manually delete the dist directory every time. We hope that every time Webpack packs, delete the previous dist directory first, and then pack it. Here we use it for processing clean-Webpack-plugin.

Similarly, it needs to be installed firstclean-Webpack-plugin

yarn add clean-Webpack-plugin -D

lg.Webpack.jsThen configure the plug-in in accordance with its usage method . First use require to import clean-Webpack-plugin, where there are too many exported things and need to be deconstructed: const { CleanWebpackPlugin } = require('clean-Webpack-plugin'). Secondly, each exported object is a class with its own constructor, which is required when used in plugins new CleanWebpackPlugin. code show as below:

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()  // 每个插件就是一个类
  ]
}

First use yarn buildto package, generate a dist directory, and then manually add an a.txt file in the dist directory. If it is deleted yarn buildafter it has worked normally.a.txtclean-Webpack-plugin

18. Use of html-webapck-plugin

In addition to the distcleaned directory, there is also a very common requirement to automatically generate HTML that uses the packaged results. Before this, HTML‌ was stored separately in the root directory of the project by hard-coding. ‌‌‌

index.htmlAfter each package is completed, the title needs to be manually modified, and the files generated by the package need to be manually modified due to the large number of file types or quantities after the package is packaged. These behaviors can be handled html-Webpack-pluginby

By default, there is no need to manually create index.htmlfiles. After using html-Webpack-pluginthe plug-in, Webpack will automatically create an index.html file in the packaged result dist directory by default.

First, manually delete the prepared index.html. When the plug-in is not used , execute yarn build to package. It is found that no files are generated in the directory html-Webpack-pluginthrough observation .distindex.html

1 Use the default index.html template

In the configuration file, first import html-Webpack-plugin.

const HtmlWebpackPlugin = require(‘html-Webpack-plugin’)

Use it in the plugins field:

const HtmlWebpackPlugin = require('html-Webpack-plugin')
...
plugins: [
  new HtmlWebpackPlugin()
]
...

At this point yarn build, the packaging process can be found that distthe index.html file already exists in the directory.
At this time, index.htmlthe content is html-Webpack-pluginprovided by default and can be found in html-Webpack-pluginnode_modules default_index.ejs.

2 Using a custom index.html template

The placeholders in the default template are described in detail in the official documentation .
For the placeholder, we can pass parameters in the plugin and give it a default value.

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 buildAfter packaging again , index.htmlthe title has been updated.
At this point we are using html-Webpack-pluginthe built-in html template file. But in actual use, we may need to use special template files. At this time, use the template field to define your own index.html template.
insert image description here
After using yarn buildthe package at this time, the custom index.htmltemplate file will be used.
At this time, the path of the website icon is used <link rel="icon" href="<%= BASE_URL %>favicon.ico">, and then used DefinePlugin(Webpack default, no need to install) to define the constant of the global configuration.

new DefinePlugin({
    
    
	BASE_URL: '"./"'
})

At this point, the complete configuration file is as follows:

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会将常量原封不动的拿走,所以需要使用引号包裹
    })
  ]
}

After packaging again, the result is as follows:
In addition to customizing the content of the output file, it is also a very common requirement to output multiple page files at the same time. In fact, the configuration is very simple, add a new HtmlWebpackPlugin object in the configuration file, the configuration is as follows:

  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'
    })
  ]

Nineteen, copy-Webpack-plugin

In the project, there are generally some static files that do not need to participate in the construction, and they also need to be published online. For example, on our website, favicon.iconthese files are generally placed in the project publicdirectory.‌ Hopefully, Webpackwhen packaging, they can be copied to the output directory as well.

‌‌For this kind of demand, you can rely on copy-Webpack-plugin,‌‌First install this plugin‌‌‌, and then import the type of this plugin,‌‌Finally add an instance of this type in the plugin attribute,‌‌The constructor of this type is It is required to pass in an array, which is used to specify the path of the file that needs to be copied. It can be a wildcard, or a directory or a relative path to a file. Plugin is used here, which means that all files will be packaged when packaging. Copy all the files in the output directory to the output directory, and run the Webpack command again. After the packaging is completed, all the files in the public directory will be copied to the output directory at the same time.

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']  // 必须写入**/, ** 两个星号的意思是在当前路径
          }
        }
      ]
    })
  ]
}


Twenty, babel use

Since the sum Webpackin the code can be processed by default , it is natural for some people to think that Webpack will automatically compile the ES6 code. In fact, it is not the case, that is, Webpack only completes the packaging work for modules, so it will do some corresponding conversions for import and export in the code, and it cannot convert other ES6 features in our code.importexport

If you need to package and compile ES6 code into ES5 code, you need some other compiled shape loaders. Install some additional plugins here.

First of all, Webpack can recognize the syntax of ES6+. Let’s test it here. Write the syntax of ES6+ in index.js and use yarn build to package it. After observing the packaged code, you can find that Webpack keeps the syntax intact index.js. ES6+The code was brought over without any processing.

Therefore, for ES6+ syntax, special tools need to be used for processing. Here, install @babel/coreand command line tools @babel/clifor code testing to see if babel will help handle ES6+ syntax by default.

yarn add @babel/core @babel/cli
yarn babel 

After using it, I found that babel still didn't help us deal with ES6+ syntax. Why? The reason is that babel also needs to use special plugins for processing.

yarn babel destination path --out-put output path

yarn babel src --out-put build

Therefore, we need special plug-ins to handle arrow functions or const and let keywords.

  • @babel/plugin-transform-arrow-functions(handling arrow functions)
  • @babel/plugin-transform-block-scoping(handles block-level scope)
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

After re-execution, it is found that the arrow function and letkeyword constscope have been processed into varkeywords.

But we found that every time we need to deal with different special cases, we need to install different babel plugins, which is very inconvenient. Therefore, babel combines most of the plug-ins related to ES6+ grammar and stage draft into a set@babel/preset-env, , and only needs to use this set to handle most of ES6+the grammar.

# 安装@babel/preset-env
yarn add @babel/preset-env
# 使用babel进行编译
yarn babel src --out-dir build --presets=@babel/preset-env

Guess you like

Origin blog.csdn.net/woyebuzhidao321/article/details/129844823