组织介绍

[TOC]

entry

入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

默认值是 ./src/index.js,但你可以通过在 webpack configuration 中配置 entry 属性,来指定一个(或多个)不同的入口起点。

output

output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

你可以通过在配置中指定一个 output 字段,来配置这些处理过程:

loader

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

plugins

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量

mode

通过选择 development, production 或 none 之中的一个,来设置 mode 参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production。

chunk

有两种形式

每个chunk都有对应的asset,就是输出文件

webpack.config.js

entry: {
    index: './src/index.js'
},

index.js

import './other.js';

console.log('I am index.js');
// 魔法字符串
import(/* webpackChunkName: "bar.js" */ './bar.js').then(res => {
    console.log(res);
}, err => {
    console.log(err);
});

上面的代码会创建一个名为 indexinitial chunk,然后 index.js 中包含other.js

还会创建一个bar.jsnon-initial chunk,因为是import动态导入的

编译后

生成asset js/index.js文件

生成asset js/bar.js文件

asset js/index.js 13.2 KiB [emitted] (name: index)
asset js/bar.js.index.js 1.17 KiB [emitted] (name: bar.js)
asset index.html 303 bytes [compared for emit]
runtime modules 6.78 KiB 9 modules
cacheable modules 517 bytes
  ./src/index.js 403 bytes [built] [code generated]
  ./src/other.js 59 bytes [built] [code generated]
  ./src/bar.js 55 bytes [built] [code generated]
webpack 5.82.0 compiled successfully in 322 ms

魔法注释

文档: https://www.webpackjs.com/api/module-methods/#magic-comments

通过在 import 中添加注释,我们可以进行诸如给 chunk 命名或选择不同模式的操作

webpackChunkName

可以给生成的 chunks 明名,如果两个 chunks 的明名相同,则最后会被打包到一个文件中

// 打包后 生成不同的 chunks 文件
import(/* webpackChunkName: "about" */'./about');
import(/* webpackChunkName: "home" */'./home');

// 打包后 会将 about.js 和 home.js 两个文件,一起打包进 other_xxx.js 文件中
import(/* webpackChunkName: "other" */'./about');
import(/* webpackChunkName: "other" */'./home');

如果只设置 webpackChunkName 魔法注释,则打包后会受到 entry 中的 filename 和 chunkFilename 的影响

  • 如果没有设置 filename 和 chunkFilename 则魔法字符串是什么,生成的文件名就是什么。
  • 如果设置了 filename 和 chunkFilename ,则按照 chunkFilename 的规则来。
  • 如果只设置了 filename ,则按照 filename 的规则来。

webpackIgnore

设置为 true 时,禁用动态导入解析。

试了一下,原本动态导入的文件,在设置了这个后就爆粗了 😅

webpackMode

有好几个值比如 'lazy' 'eager' 等等,看文档吧,基本不怎么用

  • lazy: 默认值,为每个 import() 导入的模块生成一个可延迟加载(lazy-loadable)的 chunk。
  • eager: 不会生成额外的 chunk。所有的模块都被当前的 chunk 引入,并且没有额外的网络请求。

webpackPrefetch

告诉浏览器将来可能需要该资源来进行某些导航跳转。查看指南,了解有关更多信息 how webpackPrefetch works

webpackPreload

告诉浏览器在当前导航期间可能需要该资源。 查阅指南,了解有关的更多信息 how webpackPreload works

webpackInclude

在导入解析(import resolution)过程中,用于匹配的正则表达式。只有匹配到的模块才会被打包

webpackExclude

在导入解析(import resolution)过程中,用于匹配的正则表达式。所有匹配到的模块都不会被打包

魔法注释失效

在开发环境使用import导入没有任何问题

import(/* webpackChunkName: "indexCss" */ './style/index.css');
console.log('I am index.js');
import(/* webpackChunkName: "barJs" */ './bar.js').then(res => {
    // console.log(res);
}, err => {
    console.log(err);
});

在生产环境就不行了,生产的文件还是id

assets by path js/*.js 4.07 KiB
  asset js/index.js 3.76 KiB [emitted] [minimized] (name: index)
  asset js/857.index.js 188 bytes [emitted] [minimized] (name: barJs)
  asset js/144.index.js 127 bytes [emitted] [minimized] (name: indexCss)
asset index.html 317 bytes [compared for emit]
asset css/indexCss.css 62 bytes [emitted] (name: indexCss)
...

解决:经过一番查找,发现是output中没有设置chunkFilename属性,设置一下就好了

output: {
    path: resolve(__dirname, './dist'),
    filename: 'js/index.js',
    chunkFilename: 'js/[name].[contenthash:5].js',
    clean: true,
},

2023.7.22号,发现没有配置 chunkFilename 也打包后,也可以改变 文件名,而且是根据 filename 的规则改变的,感觉太乱了

在webpack.config.js 中使用 es6 语法

webpack5

配置webpack.config.json语法推荐

  1. 先下载 npm i @babel/register --save-dev
  2. 修改webpack.config.jswebpack.config.babel.js,所有配置文件 想改为es模块的,都得改后缀为.babel.js
  3. 安装 npm i -D @babel/core @babel/preset-env,并配置.babelrc
        {
            "presets": [
                "@babel/preset-env"
            ]
        }
  4. webpack.config.babel.js中的配置,改为es6的语法
        import webpack from 'webpack';
        import path from 'path';
        /**
        * @type {webpack.Configuration}
        */
        export default {
            entry: path.resolve(__dirname, '../src/index.js'),
            output: {
                path: path.resolve(__dirname, '../dist'),
                filename: 'index.js',
                clean: true
            }
        }
  5. 配置package.json并启动
       "scripts": {
          "dev": "./node_modules/webpack/bin/webpack.js --config ./config/webpack.config.babel.js --mode development"
      },

在webpack.config.js中使用ts语法

文档:https://www.webpackjs.com/configuration/configuration-languages/#typescript

  1. 先下载依赖npm i -D typescript ts-node @types/node @types/webpack

  2. 如果需要用到webpack-dev-server,npm install --save-dev @types/webpack-dev-server

  3. 把webpack配置文件的文件名改为:webpack.config.ts

  4. webpack.config.ts改为ts语法

        import {Configuration} from 'webpack';
        import path from 'path';
        const devConfig: Configuration = {
            entry: path.resolve(__dirname, '../src/index.js'),
            output: {
                path: path.resolve(__dirname, '../dist'),
                filename: 'index.js',
                clean: true
            }
        }
    
        export default devConfig;
  5. 编写tsconfig.json 这是因为我们在前面安装的依赖ts-node不支持commonjs之外的模块语法,所以我们必须在TypeScript的配置文件tsconfig.json配置compilerOptions中module字段为:commonjs,

        {
            "compilerOptions": {
                "module": "CommonJS",
                "target": "ES5",
                "esModuleInterop": true
            }
        }
  6. 配置package.json

     "scripts": {
         "dev": "./node_modules/webpack/bin/webpack.js --config ./config/webpack.config.ts --mode development",
         "build": "npx webpack --config ./config/webpack.config.ts --mode production"
     },
  7. 启动项目即可

  8. 可能需要重启下编辑器

获取简短的Git版本哈希 git rev-parse --short HEAD

cross-env设置环境变量

这个迷你的包(cross-env)能够提供一个设置环境变量的scripts,让你能够以unix方式设置环境变量,然后在windows上也能兼容运行。

安装

npm i -D cross-env

使用

{
    "scripts": {
        "build": "cross-env NODE_ENV=production webpack --config build/webpack.config.js"
    }
}

NODE_ENV环境变量将由cross-env设置 打印process.env.NODE_ENV === 'production'

处理HTML

html-webpack-plugin

  • 文档:https://github.com/jantimon/html-webpack-plugin#options
  • npm i -D html-webpack-plugin
  • 插件的作用就是生成 html 文件,将entry配置入口相关chunk和css插入到template或templateContent指定的html文件中,link插入到head中,script插入到body或head中

插件的使用

{
    plugins: [
        new HtmlWebpackPlugin({...})
    ]
}

插件的默认参数为

const defaultOptions = {
    template: 'auto',
    templateContent: false,
    templateParameters: templateParametersGenerator,
    filename: 'index.html',
    publicPath: userOptions.publicPath === undefined ? 'auto' : userOptions.publicPath,
    hash: false,
    inject: userOptions.scriptLoading === 'blocking' ? 'body' : 'head',
    scriptLoading: 'defer',
    compile: true,
    favicon: false,
    minify: 'auto',
    cache: true,
    showErrors: true,
    chunks: 'all',
    excludeChunks: [],
    chunksSortMode: 'auto',
    meta: {},
    base: false,
    title: 'Webpack App',
    xhtml: false
};

title

默认: 'Webpack App'

类型: string

生成的html文档的标题。配置该项,它并不会替换指定模板文件中的title元素的内容,除非html模板文件中使用了模板引擎语法来获取该配置项值,如下ejs模板语法形式 <title><%= htmlWebpackPlugin.options.title %></title>

filename

默认: 'index.html'

类型: string | ((entryName: string) => string);

输出文件的文件名称,默认为index.html,不配置就是该文件名;此外,还可以为输出文件指定目录位置(例如'html/index.html')

  • filename配置的html文件目录是相对于webpackConfig.output.path路径而言的,不是相对于当前项目目录结构的。
  • 指定生成的html文件内容中的linkscript路径是相对于生成目录下的,写路径的时候请写生成目录下的相对路径。

template

默认: auto,使用的是src/index.ejs,如果没有则使用的是html-webpack-plugin提供的默认模板default_index.ejs

类型: string

- 不使用任何loader的情况下默认使用内部提供的ejs加载器解析此加载程序不支持完整的 ejs 语法因为它基于lodash 模板
{
    plugins: [
        new HtmlWebpackPlugin({
            template: 'src/index.html'
        })
    ]
}
  • 为模板设置加载器
new HtmlWebpackPlugin({
    // !表示所有的normal loader全部不执行(执行pre,post和inline loader)
    // -!表示所有的normal loader和pre loader都不执行(执行post和inline loader)
    // !! 表示所有的normal pre 和 post loader全部不执行(只执行inline loader)
    // For details on `!!` see https://webpack.js.org/concepts/loaders/#inline
    template: '!!handlebars-loader!src/index.hbs'
})
  • 使用module.rules设置加载器

设置了 html-loader 就不会按照ejs加载

{
    module: {
        rules: [{
            test: /\.html$/,
            loader: 'html-loader'
        }],
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'src/index.html'
        })
    ]
}

templateContent

默认: false

类型:

templateContent?:
      | false // Use the template option instead to load a file
      | string
      | ((templateParameters: {
          [option: string]: any;
        }) => string | Promise<string>)
      | Promise<string>;

string | function,可以指定模板的内容,不能与template共存。配置值为function时,可以直接返回html字符串,也可以异步调用返回html字符串。

templateParameters

默认: false

类型:

提供一些参数给templatetemplateContent中使用

    new HtmlWebpackPlugin({
            // ...
            templateParameters: {
                title: isDev ? '开发环境_' : '生产环境_',
                aa: 'aaaa'
            }
        	// ...
        }),
    ]
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- <title><%= htmlWebpackPlugin.options.title%></title> -->
    <title><%= title %></title>
</head>
<body>
    <div><%= aa %></div>
</body>
</html>

scriptLoading

为script添加defer或module,防止页面渲染阻塞

默认: defer

类型: "blocking" | "defer" | "module";

  • 值为defer的时候,在script上添加defer
  • 值为module的时候,在script上添加type="module"
  • blocking 什么也不添加

如果不传递inject的情况下,值为blocking的时候,会将script添加到body后,

inject

默认: userOptions.scriptLoading === 'blocking' ? 'body' : 'head'

类型:

inject?:
  | false // Don't inject scripts
  | true // Inject scripts into body
  | "body" // Inject scripts into body
  | "head"; // Inject scripts into head

默认 根据scriptLoading的值来插入

向template或者templateContent中注入所有静态资源,不同的配置值注入的位置不经相同

  • true或者body:所有JavaScript资源插入到body元素的底部
  • head: 所有JavaScript资源插入到head元素中
  • false: 所有静态资源css和JavaScript都不会注入到模板文件中

favicon

默认: false

类型: false | string;

在线生成ico

添加特定favicon路径到输出的html文档中,这个同title配置项,需要在模板中动态获取其路径值

{
    favicon: resolve(__dirname, '../src/img/favicon.ico')
}

hash

默认: false

类型: boolean

给注入的script和link标签添加hash值,有助于清除缓存

<link rel="icon" href="/favicon.ico?8618a8e481136d0b9335">
<script defer src="/js/index.js?8618a8e481136d0b9335"></script>

chunks

默认: all

类型: "all" | string[];

将entry中的入口文件,插入到html中,不传值的情况下,自动将entry中的所有文件插入

也可以传一个数组,控制插入哪些入口文件

chunks: ['index', 'app'],

excludeChunks

默认: []

类型: string[]

与chunks相反,禁止注入某些文件

excludeChunks: ['app'], // 禁止插入的文件

chunksSortMode

默认: auto

类型: "auto"|"manual"|((entryNameA: string, entryNameB: string) => number);

控制 脚本 在页面中插入的顺序 auto|manual|()=>{}

  • autonone是一个意思,都是直接输出entry中的入口文件的顺序 在源码的chunksorter.js中,有这么两行代码

    /**
     * Performs identity mapping (no-sort).
     * @param  {Array} chunks the chunks to sort
     * @return {Array} The sorted chunks
     */
    module.exports.none = chunks => chunks;
    
    /**
     * Defines the default sorter.
     */
    module.exports.auto = module.exports.none;

    module.exports.none = chunks => chunks;参数chunks就是根据entry中的keys过滤之后的数组(根据includedChunksexcludedChunks) 源码如下index.js

      // 获取到entry的keys
      const entryNames = Array.from(compilation.entrypoints.keys());
      // 过滤下 获取到entry的keys
      const filteredEntryNames = filterChunks(entryNames, options.chunks, options.excludeChunks); 
      // 根据 chunksSortMode 传递的参数 排序
      const sortedEntryNames = sortEntryChunks(filteredEntryNames, options.chunksSortMode, compilation);
      function filterChunks (chunks, includedChunks, excludedChunks) {
        return chunks.filter(chunkName => {
          // Skip if the chunks should be filtered and the given chunk was not added explicity
          if (Array.isArray(includedChunks) && includedChunks.indexOf(chunkName) === -1) {
            return false;
          }
          // Skip if the chunks should be filtered and the given chunk was excluded explicity
          if (Array.isArray(excludedChunks) && excludedChunks.indexOf(chunkName) !== -1) {
            return false;
          }
          // Add otherwise
          return true;
        });
      }
  • manual就是按照html-webpack-plugin中的chunks中的顺序排序(如果是个数组),如果不是数组,就按照entry中的keys排序 源码如下chunksorter.js

    module.exports.manual = (entryPointNames, compilation, htmlWebpackPluginOptions) => {
      const chunks = htmlWebpackPluginOptions.chunks;
      if (!Array.isArray(chunks)) {
        return entryPointNames;
      }
      // Remove none existing entries from
      // htmlWebpackPluginOptions.chunks
      return chunks.filter((entryPointName) => {
        return compilation.entrypoints.has(entryPointName);
      });
    };
  • 如果是一个函数,就是webpack.entry中的 keys,过滤后,的数组 .sort 的函数 源码如下index.js

      // 获取到entry的keys
      const entryNames = Array.from(compilation.entrypoints.keys());
      // 过滤下 获取到entry的keys
      const filteredEntryNames = filterChunks(entryNames, options.chunks, options.excludeChunks); 
      // 根据 chunksSortMode 传递的参数 排序
      const sortedEntryNames = sortEntryChunks(filteredEntryNames, options.chunksSortMode, compilation);
      function sortEntryChunks (entryNames, sortMode, compilation) {
        // Custom function 如果是个函数
        if (typeof sortMode === 'function') {
          return entryNames.sort(sortMode);
        }
        // Check if the given sort mode is a valid chunkSorter sort mode
        if (typeof chunkSorter[sortMode] !== 'undefined') {
          return chunkSorter[sortMode](entryNames, compilation, options);
        }
        throw new Error('"' + sortMode + '" is not a valid chunk sort mode');
      }

xhtml

默认: false

类型: boolean

是否渲染link为自闭合的标签,true则为自闭合标签

cache

默认: true

类型: boolean

如果为true表示在对应的thunk文件修改后就会emit文件

showErrors

默认: true

类型: boolean

错误详细信息将写入 HTML 页面

minify

默认: auto

类型: "auto" | boolean | MinifyOptions;

压缩html

publicPath

默认: auto

类型: publicPath?: string | "auto";

压缩html

{
    /* 
        By default the public path is set to `auto` - that way the html-webpack-plugin will try
        to set the publicPath according to the current filename and the webpack publicPath setting
        默认情况下,公共路径设置为“auto”,这样html webpack插件就会尝试根据当前文件名和webpack publicPath设置设置publicPath
    */
	publicPath: 'aa/bb/cc',
}

不设置的情况下,默认为auto,会自动的根据webpack的publicPath生成路径,如果设置其他的字符串,就会在注入的js、img、css等路径前拼接上该字段。

<link rel="icon" href="aa/bb/cc/mouse_cat_1.png"></head>
<script src="aa/bb/cc/js/index.js"></script></body>

meta

类型:

    meta?:
      | false // Disable injection
      | {
          [name: string]:
            | string
            | false // name content pair e.g. {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'}`
            | { [attributeName: string]: string | boolean }; // custom properties e.g. { name:"viewport" content:"width=500, initial-scale=1" }
        };

生成meta标签

{
    // ...
    meta: {
        // 生成 <meta charset="UTF-8">
        charset: {
            charset: 'UTF-8' // 指定文档使用的字符集
        },
        // 生成 <meta name="keywords" content="webpack,webpack.config.ts">
        keywords: 'webpack,webpack.config.ts', // 定义网页的关键词,是一种用于 SEO 的技术;
        description: 'html-webpack-plugin练习', // 描述网页的内容,对搜索引擎优化(SEO)和用户体验有帮助;
        viewport: 'width=device-width, initial-scale=1.0',
        author: 'qxy',
        expires: {
            'http-equiv': 'expires',
            content: '31 Dec 2008'
        },
        'content-type': {
            'http-equiv': 'Content-Type',
            content: 'text/html; charset=utf-8'
        },
        refresh: {
            'http-equiv': 'refresh',
            content: '1000; url=https://www.baidu.com'
        }
    },
    // ...
}

常用属性

new HtmlWebpackPlugin({
    title: 'xxxhtml',
    filename: 'index.html',
    template: resolve(__dirname, '../src/index.ejs'),
    inject: 'body',
    favicon: resolve(__dirname, '../src/img/favicon.ico')
})

补充

在引入html-webpack-plugin时,ts类型报错

tsconfig.json中设置

{
    "compilerOptions": {
        "esModuleInterop": true // 用于解决es模块和commonjs模块互相操作的问题,否则es模块在操作commonjs模块时,会因为语法不同而出问题,开启这个选项,ts会自动生成额外的代码,将commonjs模块转换成es模块
    }
}

处理样式

style-loader

文档更详细

一般和css-loader一起使用,css-loader将css文件样式转成可执行的commonjs模块,然后style-loader将可执行的commonjs 转变成插入html中的style标签

安装: npm i --save-dev style-loader css-loader

injectType

默认: styleTag

类型: styleTag(默认值)、singletonStyleTagautoStyleTaglazyStyleTaglazySingletonStyleTaglazyAutoStyleTaglinkTag

  • styleTag: 通过使用多个 <style></style> 自动把 styles 插入到 DOM 中,即一个 import 就会生成一个 style 标签,无论是js还是css中引入的

    import './style/index.css';
    import './style/a.css';
    <style>...</style>
    <style>...</style>
  • singletonStyleTag: 对应 styleTag,不过它是把他们整合到一个 style 标签中,如有重复的样式名不会额外处理(Source map 不起作用)

    import './style/index.css';
    import './style/a.css';
    <style>...</style>
  • autoStyleTag: 与styleTag相同,但是当代码在 IE6-9 中运行时,请打开singletonStyleTag模式。

  • lazyStyleTag: 推荐 lazy style 遵循使用 .lazy.css 作为后缀的命名约定,style-loader 基本用法是使用 .css 作为文件后缀(其他文件也一样,比如:.lazy.less.less)。

    当使用 lazyStyleTag 时,style-loader 将惰性插入 styles,在需要使用 styles 时可以通过 style.use() / style.unuse() 使 style 可用。

    import styles from "./styles.lazy.css";
    
    styles.use();
    // 要移除 styles 时你可以调用
    // styles.unuse();

    同时.css文件和.lazy.css文件,要分开成两个来处理

    {
        test: /\.css$/,
        exclude: /\.lazy\.css$/i,
        use: ['style-loader', 'css-loader']
    },
    {
        test: /\.lazy\.css$/i,
        use: [
          { loader: "style-loader", options: { injectType: "lazyStyleTag" } },
          "css-loader",
        ],
    },
  • lazySingletonStyleTag: 文档上说是问lazyStyleTag效果类似,只是最后会生成一个style标签,但是试了一下,还是多个style标签(Source maps 不起作用)

  • lazyAutoStyleTag: 与lazyStyleTag相同,但是当代码在 IE6-9 中运行时,请打开lazySingletonStyleTag模式。

  • linkTag: 使用多个 <link rel="stylesheet" href="path/to/file.css"> 将 styles 动态的插入到 DOM 中。 只不过需要配合file-loader,静态插入请使用MiniCssExtractPlugin

    {
        test: /\.css$/,
        exclude: /\.lazy\.css$/i,
        use: [
            isProduction
                ? 'style-loader'
                : {
                    loader: 'style-loader',
                    options: {
                        // styleTag(默认值)、singletonStyleTag、autoStyleTag、lazyStyleTag、lazySingletonStyleTag、lazyAutoStyleTag、linkTag
                        injectType: 'linkTag'
                    }
                },
            'file-loader'
        ]
    },

attributes

默认: {}

类型: Object

{
    loader: 'style-loader',
    options: {
        // styleTag(默认值)、singletonStyleTag、autoStyleTag、lazyStyleTag、lazySingletonStyleTag、lazyAutoStyleTag、linkTag
        // 插入 style标签的方式
        injectType: 'singletonStyleTag',
        // 自定义添加style标签上的属性
        attributes: {
            aa: 'aa'
        }
    }
}
<style aa="aa">...</style>

insert

默认: head

类型: String|Function

style标签插入的位置,默认是head标签的尾部,因为是尾部会比原本就存在html中的style标签的优先级高。

如果是String类型的,会通过query selector找到这个标签,然后插入

也可以是一个函数,自定义插入的位置,详情见官网

如果是一个函数,函数的第一个参数应该是style标签元素,第二个参数可以接收到,injectTypelazyStyleTag|lazySingletonStyleTag时,use函数传递过来的参数,详情请查阅官网

styleTagTransform

默认值undefined

类型String | Function

当将 'style' 标签插入到 DOM 中时,转换标签和 css。

如果是一个函数,函数的第一个参数是 css字符串,第二个参数是插入的style标签,然后就可以对css字符串进行操作

貌似injectTypesingletonStyleTag的时候,函数不执行

{
    test: /\.css$/,
    exclude: /\.lazy\.css$/i,
    use: [
        isProduction
            ? 'style-loader'
            : {
                loader: 'style-loader',
                options: {
                    // 将style插入html的时候,可以进项一些操作
                    styleTagTransform: function(css, style) {
                        // 在浏览器上打印查看,因为只有的执行js的情况下,才会将css插入到dom
                        console.log(111, css, style);
                        style.innerHTML = `${css} h1 {font-weight: normal;}\n`;
                        document.body.appendChild(style);
                    }
                }
            },
        'css-loader'
    ]
},

base

不清楚

esModule

默认: true

类型: Boolean

默认情况下,style-loader 生成使用 ES 模块语法的 JS 模块。在某些情况下使用 ES 模块语法更好,比如:module concatenationtree shaking 时。

常用设置

一般情况下不需要其他的配置

在开发环境下推荐使用style-loaderstyle标签直接插入到DOM中,运行的更快

在生产环境下推荐使用mini-css-extract-plugin,将CSS提取成单独的文件,以便CSS和JS可以在浏览器并行加载

不要将 style-loader mini-css-extract-plugin 一起使用。

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const devMode = process.env.NODE_ENV !== "production";
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          isProduction ? "style-loader" : MiniCssExtractPlugin.loader,
          "css-loader"
        ],
      },
    ],
  },
  plugins: [].concat(isProduction ? [] : [new MiniCssExtractPlugin()]),
};

补充

单独使用?

style-loader好像并不能单独的使用,需要配合css-loader使用

先使用css-loader处理css文件

const style = import('./style/index.css');
style.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})

打印出

{
    "default": [
        [
            "./src/style/index.css",
            "/* * {\n    box-sizing: border-box;\n} */\n\n.title {\n    color: red;\n}",
            ""
        ]
    ]
}

然后使用style-loader利用js代码插入一个style标签到页面中

<style>/* * { box-sizing: border-box; } */ .title { color: red; }</style>
nonce属性

内容安全策略(Content Security Policy,CSP)是一种用于保护 Web 应用程序免受跨站点脚本攻击(XSS)等攻击的技术。CSP 规定了哪些资源可以被加载和执行,从而限制了攻击者可以注入的恶意代码。

nonce文档

  • 可以通过 attributes 设置nonce属性
  • 也可以通过 webpack_nonce 设置
  • attributes 设置的优先级高,高到只要有 attributes 这个属性,webpack_nonce 就不会生效

attributes 方式:

style标签设置属性 attributes.nonce: '1234'

meta标签设置属性 <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'nonce-1234'">

module: {
    rules: [
        {
            test: /.css$/,
            exclude: /node_modules/,
            use: [
                {
                    loader: 'style-loader',
                    options: {
                        attributes: {
                            nonce: '1234'
                        },
                    }
                },
                {
                    loader: 'css-loader',
                    options: {
                        sourceMap: true
                    }
                }
            ]
        }
    ]
},
plugins: [
    new HtmlWebpackPlugin({
        title: isDev ? '开发环境' : '生产环境',
        template: './src/index.html',
        filename: 'index.html',
        meta: {
            httpEquiv: {
                'http-equiv': 'Content-Security-Policy',
                content: "default-src 'self'; style-src 'nonce-1234'"
            }
        }
    })
]
使用模块化
插入style标签的几种方式

css-loader

css-loader 会对 @importurl() 进行处理,就像 js 解析 import/require() 一样。

安装: npm install --save-dev css-loader

文档: https://github.com/webpack-contrib/css-loader

url

文档:https://github.com/webpack-contrib/css-loader#url

默认值: true

类型:

type url =
  | boolean
  | {
      filter: (url: string, resourcePath: string) => boolean;
    };

允许启用/禁用处理 CSS 函数urlimage-set. 如果设置为false

h1 {
    color: red;
    background: url('~@/img/cat.webp') no-repeat;
}
=>
h1 {
    color: red;
    background: url(file:///Users/v_qianxinyu/Desktop/qxy-fe/webpack-study/webpack-3/webpack-exercise/dist/img/cat.bc934.webp) no-repeat;
}

如果路径中使用了别名,需要加上前缀~

h1 {
    color: red;
    background: url('~@/img/cat.webp') no-repeat;
}

补充:image-setsrcset的作用,都是为不同分辨率的设备匹配图像

image-set: 文档

srcset: 文档

import

文档

默认: true

类型:

type import =
  | boolean
  | { filter: (url: string, media: string, resourcePath: string) => boolean };

是否解析 @import 和 @import url() 的样式导入

modules

文档

默认: undefined

类型: 详见文档,太多了

是否启用模块化

const cssLoader = {
    loader: 'css-loader',
    options: {
        // modules: true, // 开启模块化
        modules: {
            // 默认 local
            // 
                // `local`(默认值): `CSS Modules` 会默认开启局部作用域,所有**全局变量**都要加上 `:global`前缀;
                // `global`: `CSS Modules` 会默认开启全局作用域,所有**局部变量**都要加上 `:local`前缀;
                // `pure`: 选择器必需包含至少一个局部 class 或者 id;
                // `icss`:`icss` 只会编译低级别的`Interoperable CSS` 格式,用于声明 CSS 和其他语言之间的 `:import `和 `:export`依赖项。
            //
            // mode: "local",
            mode: (resourcePath) => {
                console.log(1111, resourcePath);
                if (/(\/src\/styles|\/node_modules\/)/i.test(resourcePath)) {
                  return "global";
                }
                return "local";
            },


            // 默认 undefined
            //
                // undefined-  为所有文件启用 CSS 模块。
                // true- 为所有匹配的文件 正则表达式启用 CSS 模块
                // false- 禁用 CSS 模块
                // RegExp- 为所有匹配/RegExp/i.test(filename)正则表达式的文件启用 CSS 模块。
                // function- 根据满足过滤器功能检查的文件名为文件启用 CSS 模块。
            //
            // auto 因为正则的默认值,文件中有 .module. 的会自动当成模块化处理,即使不手动配置modules
            // auto: (resourcePath) => {
            //     // 表示 文件名为 .global.less|css 的文件不开启模块化
            //     return !(resourcePath.endsWith(".global.css") || resourcePath.endsWith(".global.less"))
            // },


            // 默认 false 允许css-loader从全局类或 id 中导出名称,因此您可以将其用作本地名称(不懂)
            // exportGlobals: true,


            // 生成类名的格式
            // 默认 [hash:base64]
            //
            	// 开发建议: [path][name]__[local]--[hash:base64:5]
            	// 生产建议:[hash:base64]
           	//
            localIdentName: "[path][name]__[local]--[hash:base64:5]",
            

            // 默认 compiler.context 允许为本地标识名称重新定义基本加载程序上下文。
            // localIdentContext: path.resolve(__dirname, "src"),
            

            // 默认:undefined   允许添加自定义哈希以生成更多独特的类
            // 自定义生成 hash的规则
            // localIdentHashSalt: undefined,

            // namedExport、exportLocalsConvention 设置用户在js中使用类名的方式
            // 默认:false 为本地人启用/禁用名为 export 的 ES 模块。
            // 意思就是 是否可以在js中 以结构赋值的使用方式获取类名,此时 exportLocalsConvention 的值必须是 camelCaseOnly
            // 如果为true的话,可以这样获取类名 import {app} from './index.module.less'
            namedExport: false,


            // 在js中的 使用方式 'camelCase' 表示 既可以驼峰使用,也可以字符串使用
            // 可以在导入css文件时,查看导入的对象中 有几种格式
            //
            	// 'asIs'	类名将按原样导出。
                // 'camelCase'	类名将被骆驼化,原始类名不会从本地人中删除
                // 'camelCaseOnly'	类名将被驼峰化,原来的类名将从本地人中删除
                // 'dashes'	只有类名中的破折号会被驼峰化
                // 'dashesOnly'	类名中的破折号将被驼峰化,原始类名将从本地人中删除
            //
            exportLocalsConvention: "camelCase",
            

            // 默认:false 不是预渲染(ssr),可以关掉
            exportOnlyLocals: false,
        },
        // postcss-loader 文档说,想要正常工作要给css-loader加上这个属性
        importLoaders: 2
    }
};

esModule

默认: true

类型: type esModule = boolean;

将css文件,解析成es模块语法的js代码,设置为false,就是commonjs语法

exportType

默认: array

类型: type exportType = "array" | "string" | "css-style-sheet";

不懂,应该是将css文件转换成js后,导出的形式,'array'(数组) | 'string'(字符串) | 'css-style-sheet'(样式对象)

导出后,给下一个loader使用,比如style-loader,我猜的

sourceMap

默认是跟 webpack配置项 中的 devtool 保持一致

importLoaders

类型Number

默认0

importLoaders 选项允许你配置在 css-loader 之前有多少 loader 应用于 @imported 资源与 CSS 模块/ICSS 导入。

从右往左数

常用设置

自己写的话,一般不用配置,直接使用即可

补充

配置css作用域
// webpack.config.js
{
    module: {
        rules: [
            {
                test: /\.css$/,
                // 不处理文件名包含有 node_modules 和 .global.css 的文件
                exclude: /(node_modules|\.global\.css$)/,
                use: [
                    // 生产环境下使用 mini-css-extract-plugin,开发环境下使用 style-loader
                    isProduction ? miniCssExtractLoader : styleLoader,
                    {
                        loader: 'css-loader',
                        options: {
                            modules: {
                                // 所有的css文件,开启局部作用域
                                mode: 'local',
                                // 生成局部类名的格式
                                localIdentName: isProduction ? '[hash:base64]' : '[path][name]__[local]--[hash:base64:5]',
                                // 自动转驼峰,并且不删除原有的类名
                                exportLocalsConvention: 'camelCase'
                            },
                        }
                    },
                ]
            },
            {
                // 文件名中包含 .global.css 的为全局css
                test: /\.global\.css$/,
                exclude: /(node_modules)/,
                use: [
                    // 生产环境下使用 mini-css-extract-plugin,开发环境下使用 style-loader
                    isProduction ? miniCssExtractLoader : styleLoader,
                    'css-loader'
                ]
            },
        ]
    }
}
<h1 class="title">
    <div class="abc"><%= title %></div>
</h1>
/* index.css */
/* 局部css */
.title {
    color: red;
    background: url('../../../../static/mouse_cat_1.png');
}

.h1-title {
    font-size: 22px;
    /* 在本地作用域中,给全局作用域的类名设置样式 */
    :global .abc {
        border: 10px solid black;
    }
}
/* style.global.css */
/* 全局 */
* {
    box-sizing: border-box;
}
.flex {
    display: flex;
}
.align-items-center {
    align-items: center;
}
.justify-content-center {
    justify-content: center;
}
// index.js
import './style/style.global.css'; // 全局css
import style from '@/style/index.css'; // 局部css

console.log(style);

const title = document.querySelector('.title');
title.classList.add(style.title) // 局部css添加类名
title.classList.add(style.h1Title)
title.classList.add('flex') // 全局css添加类名
title.classList.add('justify-content-center')

less-loader

将less转换成css

安装: npm install --save-dev less-loader

less-loader会一起安装less包,也可以手动在安装一次npm i -D less

{
    test: /\.less/,
    use: [
        isProduction ? 'style-loader' : 'style-loader',
        'css-loader',
        'less-loader'
    ]
},

lessOptions

详见文档

就是传入一些less的options

export const lessLoader = {
    loader: 'less-loader',
    options: {
        // 定义一些less的配置
        lessOptions: {
            // 比如这个,定义less的全局变量,在样式中,直接 @dark、@red 使用
            globalVars: {
                dark: '#000',
                red: '#f00'
            }
        }
    }
}

additionalData

详见文档

入口文件的起始位置添加 Less 代码

export const lessLoader = {
    loader: 'less-loader',
    options: {
        // 可以添加一些变量,入口文件的起始位置添加 Less 代码,在代码中可以直接使用 @font-size-14
        // 也可以是一个函数,更加方便的决定添加变量的位置
        // 详见文档 https://webpack.docschina.org/loaders/less-loader/#additionaldata
        additionalData: `@font-size-14: 14px;`,
    }
}

sourceMap

详见文档

类型: Boolean 默认值: 取决于 compiler.devtool 的值

默认生成的 source map 取决于 compiler.devtool 的值。除了值等于 evalfalse 外,其他值都能生成 source map

webpackImporter

详见文档

启用/禁用 webpack 默认的 importer。

implementation

详见文档

特殊的 implementation 选项决定使用 Less 的哪个实现。重载本地安装的 lesspeerDependency 版本。

此选项只对下游的工具作者有效,以便于 Less 3 到 Less 4 的过渡。

常用

export const lessLoader = {
    loader: 'less-loader',
    options: {
        sourceMap: true, // 简单设置即可
    }
}

sass-loader

将sass转换成css

安装: npm install --save-dev sass sass-loader

直接安装sass-loader会自动的安装上sass,如果想要控制sass的版本,在单独安装sass也行,安装sass就相当于是安装了Dart Sass,webpack文档上推荐使用Dart Sass文档

{
    test: /\.less/,
    use: [
        isProduction ? 'style-loader' : 'style-loader',
        'css-loader',
        'sass-loader'
    ]
},

implementation

文档

选项确定要使用的 Sass 实现,sass 或 node-sass

import {LoaderOptions} from 'sass-loader/interfaces';

export const sassLoader = {
    loader: 'sass-loader',
    options: {
        // 选项确定要使用的 Sass 实现,sass 或 node-sass
        // 默认sass,会自动的读取package.json中安装了 sass还是node-sass,因此安装sass-loader的时候,最好也一起安装sass
        implementation: 'sass'
    } as LoaderOptions
}

sassOptions

文档

lessOptions类似,就是设置sass的配置项,Dart SassNode-Sass配置项不同

import {LoaderOptions} from 'sass-loader/interfaces';

export const sassLoader = {
    loader: 'sass-loader',
    options: {
        // 设置sass的配置项
        sassOptions: {
            // 用于确定用于缩进的空格或制表符的数量。
            indentWidth: 4,
            // 用于确定是使用空格还是制表符进行缩进。
            indentType: 'space'
        }
    } as LoaderOptions
}

sourceMap

文档

如果为 true 将会忽略来自 sassOptions 的 sourceMap,sourceMapRoot,sourceMapEmbed,sourceMapContents 和 omitSourceMapUrl 选项。

import {LoaderOptions} from 'sass-loader/interfaces';

export const sassLoader = {
    loader: 'sass-loader',
    options: {
        // 如果为 true 将会忽略来自 sassOptions 的 sourceMap,sourceMapRoot,sourceMapEmbed,sourceMapContents 和 omitSourceMapUrl 选项。
        sourceMap: true,
    } as LoaderOptions
}

additionalData

文档

在实际的文件之前要添加的 Sass / SCSS 代码。 在这种情况下,sass-loader 将不会覆盖 data 选项,而只是将它拼接在入口文件内容之前。

总是报错没成功过。。。

webpackImporter

开启 / 关闭默认的 Webpack importer。

warnRuleAsWarning

@warn 规则视为 webpack 警告。

常用

import {LoaderOptions} from 'sass-loader/interfaces';

export const sassLoader = {
    loader: 'sass-loader',
    options: {
        sourceMap: true
    } as LoaderOptions
}

postcss-loader

PostCSS 是一个允许使用 JS 插件转换样式的工具。 这些插件可以检查(lint)你的 CSS,支持 CSS Variables 和 Mixins, 编译尚未被浏览器广泛支持的先进的 CSS 语法,内联图片,以及其它很多优秀的功能。

安装: npm install --save-dev postcss-loader postcss

execute

如果你在 JS 文件中编写样式,请使用 postcss-js parser,并且添加 execute 选项。

postcssOptions

允许设置 PostCSS 选项 和插件。

sourceMap

类型:Boolean 默认值:依赖于 compiler.devtool 的值

implementation

特殊的 implementation 选项决定使用 PostCSS 哪个实现。重载本地安装的 postcsspeerDependency 版本。

插件

这里简单的设置下,可以查看另一篇,postcss学习,其中有些插件的用法并不适合在webpack中使用,谨慎

  • 首先一定要设置需要兼容的浏览器版本

    • 可以在package.json 或者 .browserslistrc 或者 postcss.config.js 或者 webpack.config.ts 中设置,个人比较喜欢在package.json中设置,因为其他包可以共享此设置了。 package.json 中

      {
          "browserslist": [
              "last 5 versions",
              "> 0.1%",
              "not dead",
              "ie>=8"
          ]
      }

      .browserslistrc 中

      "last 5 versions",
      "> 0.1%",
      "not dead",
      "ie>=8"

      postcss.config.ts 中

      import postcssPresetEnv from 'postcss-preset-env';
      
      module.exports = {
          plugins: [
              postcssPresetEnv({
                  browsers: [
                      "last 5 versions",
                      "> 0.1%",
                      "not dead",
                      "ie>=8"
                  ]
              })
          ]
      }

      webpack.config.ts 中

      import postcssPresetEnv from 'postcss-preset-env';
      
      export const postcssLoader = {
          loader: 'postcss-loader',
          options: {
              postcssOptions: {
                  plugins: [
                      [
      					// 在postcss.config.js中配置postcss比较好,在loader中配置容易有坑
                          postcssPresetEnv({
                              // preserve: true,
                              browsers: [
                                  "last 5 versions",
                                  "> 0.1%",
                                  "not dead",
                                  "ie>=8"
                              ]
                          })
                      ]
                  ]
              }
          }
      }
  • 然后在设置插件,比如 postcss-preset-env ,cssnano,stylelint等等,对css使用的插件,基本就是自动添加前缀、压缩css、校验格式等。

    上面这样看来,有些原本直接在postcss中使用的插件,并不适合在webpack中直接使用,需要找相应的webpack插件,因此postcss在webpack中的插件配置就简单了许多,如下:

    推荐在postcss.config.js中配置postcss,在postcss-loader中遇到很多的坑,因此就放弃在postcss-loader中配置了

    上面说的不适合不太正确,是因为我在postcss-loader中配置的时候,代码格式写错了,导致的运行结果异常,postcss的配置既可以在配置文件中配置,也可以在postcss-loader中配置,看情况而定。

    // postcss.config.js
    const {pluginOptions} = require('postcss-preset-env');
    module.exports = {
        plugins: [
            // https://github.com/csstools/postcss-preset-env
            [
                'postcss-preset-env',
                /** * @type {pluginOptions} */ ({
                    // preserve: true,
                })
            ],
            // https://cssnano.co/docs/introduction/
            // 'cssnano', // 感觉不如 css-minimizer-webpack-plugin 好用,就不用这个了
        ]
    }

    or

    // postcss-loader
    import postcssPresetEnv from 'postcss-preset-env';
    import cssnano from 'cssnano';
    
    const postcssLoader = {
        loader: 'postcss-loader',
        options: {
            // implementation: require('postcss'),
            sourceMap: true,
            postcssOptions: {
                plugins: [
                    // 这样写 属性 有语法提示,比较友好
                    postcssPresetEnv({
                        // preserve: true,
                        // 在 package.json 或 .browserslistrc 中设置即可
                        // browsers: [
                        //     "last 5 versions",
                        //     "> 0.1%",
                        //     "not dead",
                        //     "ie>=8"
                        // ]
                    }),
                    cssnano(),
                ]
            }
        }
    }

补充

css-minimizer-webpack-plugincssnano

如果使用了css-minimizer-webpack-plugin,就可以不用设置 cssnano,因为css-minimizer-webpack-plugin默认就是使用的cssnano压缩的css

webpack中推荐使用css-minimizer-webpack-plugin

下面的问题是本人粗心大意代码格式写错,搞出的问题,先留着,万一又遇到了呢

如果在postcss-loader中直接设置cssnano,会出现一些问题,比如压缩不生效,自动加前缀不生效等等,推荐在postcss.config.js中设置postcss的配置项,就没有这些问题

  1. 需要放在 postcss-preset-env 插件之前才生效,具体可能原因如下:

    将cssnano插件放在postcss-preset-env插件之前是因为这样可以确保cssnano在postcss-preset-env之前对CSS进行压缩和优化。具体来说,postcss-preset-env会根据指定的浏览器兼容性列表自动添加浏览器前缀、转换CSS新特性,并通过一些polyfill满足某些浏览器不支持的特性。这个过程会涉及到对CSS的重写和添加,可能会造成cssnano无法正确地识别CSS代码,因此必须将cssnano插件放在postcss-preset-env插件之前,确保在添加浏览器前缀和转换新特性之前对CSS进行压缩和优化。

    另外,需要注意的是,虽然将cssnano插件放在postcss-preset-env插件之前可以解决一些问题,但在实际开发中,还需要根据具体情况进行优化和调整。例如,如果使用了其他的插件对CSS进行了复杂的处理,那么可能需要进一步调整插件的顺序和配置,以做到最佳的优化效果。

  2. 虽然压缩生效了,但是postcss-preset-env的一些功能却失效了,比如添加前缀

    一个常见的例子就是自动添加浏览器前缀的功能。在postcss-preset-env插件处理CSS之前,cssnano插件可能已经对CSS进行了压缩和优化,但是其中可能包含了一些不兼容的CSS代码,需要通过postcss-preset-env插件进行转换和添加浏览器前缀。但由于cssnano插件已经对这些CSS代码进行了处理,postcss-preset-env插件可能会错过这些代码,导致浏览器兼容性问题。

解决,不使用cssnano插件,使用其他压缩css的插件

// postcss-loader 配置
import postcss from 'postcss';
import postcssPresetEnv from 'postcss-preset-env';
import cssnano from 'cssnano';

export const postcssLoader = {
    loader: 'postcss-loader',
    options: {
        implementation: postcss, // 不经常设置,第三方的库可能会设置 less-loader、sass-loader 等都是
        sourceMap: true,
        // 在postcss.config.js中配置postcss比较好,在loader中配置容易有坑
        postcssOptions: {
            plugins: [
                [
                    // cssnano(),
                    // 这样写 属性 有语法提示,比较友好
                    postcssPresetEnv(),
                ]
            ]
        },

    }
}

使用 css-minimizer-webpack-plugin

// webpack.config.ts
import CssMinimizerWebpackPlugin from 'css-minimizer-webpack-plugin';
export default = {
    // ...
    optimization: {
        minimizer: [
            // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
            `...`,
            // 生产模式下 压缩css
            // TODO 很奇怪,在文档上看是要写在这儿的,但是开发模式下即使minimize: true,也不生效,写在plugin中就没有这个问题
            new CssMinimizerWebpackPlugin(),
        ],
        // !坑,如果需要开发模式下也压缩,在设置这个属性,否则不要乱设置,可能会造成压缩失效
        // minimize: true, // true 开发模式下也压缩
    },
}

最终配置

  • postcss-loader 在rules中配置

    // webpack.config.js
    import postcss from 'postcss';
    export const postcssLoader = {
        loader: 'postcss-loader',
        options: {
            implementation: postcss, // 不经常设置,第三方的库可能会设置 less-loader、sass-loader 等都是
            sourceMap: true,
    }
  • 配置.browserslistrc,一定要配置,否则有些功能不起作用

    last 5 versions
    > 0.01%
    not dead   
  • 使用css新特性,自动添加前缀,也可以在postcss-loader中配置

    // postcss.config.js
    const {pluginOptions} = require('postcss-preset-env');
    const tailwindcss = require('tailwindcss');
    module.exports = {
        plugins: [
            // tailwindcss ui库的配置,不需要直接删掉即可,tailwindcss 还需要 tailwind.config.js 配置文件
            // https://tailwindcss.com/docs/using-with-preprocessors#nesting
            'tailwindcss/nesting',
            // https://tailwindcss.com/docs/installation
            tailwindcss(),
    
            // https://github.com/csstools/postcss-preset-env
            [
                'postcss-preset-env', // 使用css的新特性,自动添加前缀等等
                /** * @type {pluginOptions} */ ({
                    // preserve: true,
                })
            ],
    
            // https://cssnano.co/docs/introduction/
            // 'cssnano', // 感觉不如 css-minimizer-webpack-plugin 好用,就不用这个了
        ]
    }
  • 压缩css

    // webpack.config.js
    import CssMinimizerWebpackPlugin from 'css-minimizer-webpack-plugin';
    module.exports = {
        // ...
        optimization: {
            minimizer: [
                // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
                `...`,
                // 生产模式下 压缩css
                // TODO 很奇怪,在文档上看是要写在这儿的,但是开发模式下即使minimize: true,也不生效,写在plugin中就没有这个问题
                new CssMinimizerWebpackPlugin(),
            ],
            // !坑,如果需要开发模式下也压缩,在设置这个属性,否则不要乱设置,可能会造成压缩失效
            // minimize: true, // true 开发模式下也压缩
        },
    }
  • 校验css格式,参考下面的 stylelint

stylelint

在webpack中使用stylelint,需要借助stylelint-webpack-plugin插件

stylelint-webpack-plugin插件实际上也是调用的stylelint.lint(options)的api进行校验的,因此插件的参数跟stylelint.lint的参数基本一致。在此只写了在webpack中如何使用

步骤

  • 首先要安装 stylelint stylelint-webpack-plugin stylelint-config-standard 等等需要用到的插件

  • 项目根目录下创建 stylelint.config.js 配置文件

  • webpack 中配置 stylelint-webpack-plugin 插件

  • 安装 stylelint vscode 插件,Stylelint

    // 需要再vscode中配置
    // stylelint
    "stylelint.validate": ["css", "less", "scss", "sass", "stylus"],
    
    // 设置为true,保存是自动格式化
    "editor.codeActionsOnSave": {
        "source.fixAll.stylelint": false, // stylelint 的配置
    },

安装

npm install stylelint-webpack-plugin stylelint --save-dev

查找配置

优先级从上到下

  • package.json 中的 stylelint 属性
  • .stylelintrc 文件
  • .stylelintrc.{cjs,js,json,yaml,yml} 文件
  • stylelint.config.{cjs,mjs,js} 文件

stylelintignore

可以通过在项目根目录创建一个 .stylelintignore 文件告诉 stylelint 去忽略特定的文件和目录。.stylelintignore 文件是一个纯文本文件,其中的每一行都是一个 glob 模式表明哪些路径应该忽略检测。(默认忽略node_modules)

globs 匹配使用 node-ignore: https://github.com/kaelzhang/node-ignore

stylelint.config.js

常用的也就没几项,比如rules、extends、plugins

rules

规则列表: https://stylelint.io/user-guide/rules/list

extends

可以继承其他已经配置好的stylelint的配置,比如官方推荐的配置stylelint-config-standard

值可以是npm包,也可以是一个相对/绝对路径

{
    "extends": "stylelint-config-standard",
    "rules": {
    	// 可以修改一些规则
    }
}
plugins

用于自定义一些插件,比如有些类css语法,sass/scss/less等预处理器的语法跟css还是不一致的,css的校验规则可能并不能完好的校验,因此就需要引用一些插件,帮助校验。

awesome-stylelint: https://github.com/stylelint/awesome-stylelint#plugins

值可以是npm包,也可以是一个相对/绝对路径

{
    "plugins": ["stylelint-scss"],
    "rules": {
        "scss/dollar-variable-pattern": "^foo",
        "scss/selector-no-redundant-nesting-selector": true
    }
}
customSyntax

指定要在代码中使用的自定义语法

const lessSyntax = require('postcss-less');
const scssSyntax = require('postcss-scss');

module.exports = {
        extends: ['stylelint-config-standard'/* , '@ecomfe/stylelint-config', */ /* , './config/stylelint.js' */],
        overrides: [
            {
                files: ['**/*.css'],
            },
            {
                files: ['**/*.less'],
                customSyntax: lessSyntax, // npm i -D postcss-less 识别除了css以外的,类似css的语法,比如less/sass等
            },
            {
                files: ['**/*.scss'],
                customSyntax: scssSyntax, // npm i -D postcss-scss 
            },
        ],
}
defaultSeverity

修改未设定警告级别的规则的默认警告级别

{
    "defaultSeverity": "warning"
}
overrides
const lessSyntax = require('postcss-less');
const scssSyntax = require('postcss-scss');

module.exports = {
        extends: ['stylelint-config-standard'/* , '@ecomfe/stylelint-config', */ /* , './config/stylelint.js' */],
        overrides: [
            {
                files: ['**/*.css'],
            },
            {
                files: ['**/*.less'],
                customSyntax: lessSyntax,
                rules: [
                    // ...
                ]
            },
        ],
    	rules: [
            // ...
        ]
}
ignoreFiles

置忽略检查的文件列表,node_modules为默认默认忽略目录,同样的还可以通过.stylelintignore去配置

{
    "ignoreFiles": ["**/*.js"]
}
ignoreDisables

设置是否允许注释配置

{
    "ignoreDisables": true
}
常用属性

stylelint.config.js

// import lessSyntax from 'postcss-less'; // 不识别es模块
const lessSyntax = require('postcss-less');
const scssSyntax = require('postcss-scss');

// ./config/stylelint.js 中的配置就是 @ecomfe/stylelint-config 中的配置
module.exports = {
    // 有一些已经写好的 第三方校验规则,可以通过extends使用,可以是一个包名称,也可以直接引入文件路径
    extends: ['stylelint-config-standard'/* , '@ecomfe/stylelint-config', */ /* , './config/stylelint.js' */],
    overrides: [
        {
            files: ['**/*.css'],
        },
        {
            files: ['**/*.less'],
            customSyntax: lessSyntax, // npm i -D postcss-less 识别除了css以外的,类似css的语法,比如less/sass等
        },
        {
            files: ['**/*.scss'],
            customSyntax: scssSyntax, // npm i -D postcss-scss 
        },
    ],

    rules: {
        // 选择器伪类无未知
        'selector-pseudo-class-no-unknown': [
            true,
            {
                ignorePseudoClasses: ['global'],
            },
        ],
        // 不允许未知的属性值
        'declaration-property-value-no-unknown': [
            true,
            {
                ignoreProperties: [],
            }
        ],
        /* 
            颜色
        */
        // 颜色值缩写
        'color-hex-length': 'short',
        // 限制单行声明块中的声明数量
        'declaration-block-single-line-max-declarations': 1,
    }
};

stylelint-webpack-plugin

// webpack.config.js
{
    plugins: [
        // ...
        // stylelint检测css
        // https://stylelint.io/user-guide/options#allowemptyinput,参数就是stylelint.lint()的参数
        new StylelintWebpackPlugin({
            // 当glob模式不匹配任何文件时,Stylelint不会抛出错误。
            // allowEmptyInput: false,

            // 指定 stylelint 配置文件的位置。一般不用设置,由stylelint去查找配置文件
            // configFile: './stylelint.config.js', // 默认值:undefined

            // 指定文件根目录的字符串。
            // context: resolve(__dirname, s'./src'), // 默认值:compiler.context

            // 指定要忽略的文件或目录。必须相对于 options.context。
            // exclude: [], // 默认值:['node_modules', compiler.options.output.path]

            // 指定要检查的扩展名。
            // extensions: ['scss'], // 默认值:['css', 'scss', 'sass']

            // 可指定为目录,文件名或 globs。目录会递归地寻找与 options.extensions 相匹配的文件。文件名和 glob 模式将忽略 options.extensions。
            files: ['**/*.scss'], // 默认值:'**/*.(s(c|a)ss|css)'
            // code: '', // 一段css代码,files和code二选一

            // 指定要在代码中使用的自定义语法。
            // customSyntax: 'postcss-less',

            // 如果值为 true,stylelint 将修复尽可能多的 error。修复真实的源文件。报告所有未修复的 errors。请参阅 自动修复错误 文档。
            fix: false, // 默认值:false,一般都是false,需要手动修改

            // 指定要用于格式化结果的 formatter。请参阅 formatter 选项。
            formatter: 'string', // 默认值:'string'

            // 设置为 true 时,会根据 cpu 的数量自动决定池子的大小。设置为大于 1 的数字时,可以确定池子的大小。设置为 false、1 或更小时,会禁用池子,并只在主线程运行。
            threads: true, // 默认值:false

            cache: true, // 开启缓存

            cacheLocation: resolve(__dirname, './node_modules/.cache/stylelint-cache'), // 缓存保存目录

            // 仅检查有变化的文件,启动时跳过检查。
            // lintDirtyModulesOnly: true, // 默认值:false

            // 如遇到错误将会被直接输出,如需禁用,请设置为 false。
            // emitError: true, // 默认值:true

            // 如果有任何错误,都将导致模块构建失败,如需禁用,请设置为 false。
            // failOnError: true, // 默认值:true

            // 如遇到警告将会被直接输出,如需禁用,请设置为 false。
            // emitWarning: true, // 默认值:true

            // 如果设置为 true,出现任何 warnings 都将会导致模块构建失败。
            // failOnWarning: false, // 默认值:false

            // 如果设置为 true,则仅处理和报告 errors,并忽略 warnings。
            // quiet: false, // 默认值:false

            // 将输出的错误写入文件,例如,用于上报的 json 文件。 其 filePath 会相对于 webpack 配置中的:output.path. 你可以为输出文件设置不同的 formatter,如果未设置,则将使用默认 formatter。
            // outputReport: false, // 默认值:false
        }),
    ]
}
常用属性
{
    plugins: [
        // ...
        // stylelint检测css
        // https://stylelint.io/user-guide/options#allowemptyinput,参数就是stylelint.lint()的参数
        new StylelintWebpackPlugin({
            // 可指定为目录,文件名或 globs。目录会递归地寻找与 options.extensions 相匹配的文件。文件名和 glob 模式将忽略 options.extensions。
            files: ['**/*.css', '**/*.less', '**/*.scss'], // 默认值:'**/*.(s(c|a)ss|css)'

            // 设置为 true 时,会根据 cpu 的数量自动决定池子的大小。设置为大于 1 的数字时,可以确定池子的大小。设置为 false、1 或更小时,会禁用池子,并只在主线程运行。
            threads: true, // 默认值:false
			
            // 插件内部已经默认开启了缓存,可以不用设置
            // cache: true, // 开启缓存
            // cacheLocation: resolve(__dirname, './node_modules/.cache/stylelint-cache'), // 缓存保存目录
        }),
    ]
}

prettier

格式化代码的工具

  • 安装: npm install --save-dev --save-exact prettier 锁定prettier的版本

  • 创建.prettierrc文件,也不是必须创建,得看是使用什么方式进行校验代码的,比如本次设置的就是在webpack中配合stylelint一起使用

  • 创建.prettierignore文件,也不是必要的

    # https://www.prettier.cn/docs/ignore.html
    node_modules
    dist
    build
    coverage
  • 编辑器安装prettier插件,使用起来更方便

  • 下面是重点,上面几条除了安装以外,其他的都可有可无

  • 本次是要配合stylelint一起校验,因此要下载插件stylelint-prettier插件

    • stylelint-prettier文档: https://github.com/prettier/stylelint-prettier

    • 安装: npm install --save-dev stylelint-prettier

    • 添加prettier的校验规则,任选一种就行

      • 第一种,直接在stylelint.config.js中配置prettier的规则,在prettier/prettier中

        // 在 sytlelint.config.js 中设置
        {
        	"plugins": ["stylelint-prettier"],
        	"rules": {
        		'prettier/prettier':[
                    true,
                    /* 
                        prettier设置 https://prettier.io/docs/en/options.html
                    */
                    {
                        "tabWidth": 4, // 缩进 4
                        "useTabs": false, // false 缩进使用空格,true使用制表符
                        "singleQuote": false, // 使用单引号代替双引号
                        "semi": true, // 在语句末尾打印分号
                        "printWidth": 120, // 指定每行的最大列数
                        "endOfLine": "lf", // 设置行尾符号类型
                    }
                ]
          	}
        }

        既然是将prettier中的规则插入到stylelint中去校验,因此需要将vscode编辑器的校验更改为stylelint的校验

        {
        	"[css]": {
                "editor.defaultFormatter": "stylelint.vscode-stylelint"
            },
        }
      • 第二种,在prettier的配置文件中设置,比如.prettierrc中设置 首先就不用在stylelint.config.js中设置prettier的规则了

        // 在 sytlelint.config.js 中设置
        {
        	"plugins": ["stylelint-prettier"],
        	"rules": {
        		'prettier/prettier': true
          	}
        }

        然后在.prettierrc中设置prettier的规则

        {
            "overrides": [
                {
                    "files": ["src/**/*.{css,less,scss}"],
                    "options": {
                        "tabWidth": 4, // 缩进 4
                        "useTabs": false, // false 缩进使用空格,true使用制表符
                        "singleQuote": false, // 使用单引号代替双引号
                        "semi": true, // 在语句末尾打印分号
                        "printWidth": 120, // 指定每行的最大列数
                        "endOfLine": "lf", // 设置行尾符号类型
                    }
                }
            ]
        }

        这个没有将规则直接插入到stylelint中,因此需要更改编辑器的的校验程序为prettier

        {
            "[css]": {
                "editor.defaultFormatter": "esbenp.prettier-vscode"
            },
        }
  • 然后就是设置校验命令 -f verbose 会输出校验是否成功 --fix 会自动修复

    // package.json中
    {
        "scripts": {
            "lint": "stylelint -f verbose src/**/*.{css,less,scss}",
            "lint:fix": "stylelint src/**/*.{css,less,scss} --fix"
        },
    }

设置git提交校验

推荐文章:

步骤:

  1. 安装husky: npm install husky -D

  2. 执行: npm set-script prepare "husky install" 就是在package.json中的scripts添加了一行

    {
    	"scripts": {
        	"prepare": "husky install"
      	},
    }
  3. 执行npm run prepare

这个命令做了什么事呢 ?实际上就是创建 .husky 目录,修改了.git/config文件里的hooksPath字段(设置 .husky 目录为 git hooks 目录)

  1. 执行: npx husky add .husky/pre-commit "npx lint-staged --allow-empty" 这个命令就是在.husky/pre-commit中添加了文本npx lint-staged --allow-empty,每次git commit都会执行pre-commit这个钩子中的内容

  2. 安装lint-staged: npm i lint-staged -D

  3. package.json中设置

"lint-staged": {
    "src/**/*.{css,less}": [
		"stylelint src/**/*.{css,less}"
    ]
}
  1. 提交一下css的代码试试。。。

mini-css-extract-plugin

本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。

安装: install --save-dev mini-css-extract-plugin @types/mini-css-extract-plugin

plugin

filename

类型{String|Function}

默认: [name].css

此选项决定了输出的每个 CSS 文件的名称,只能决定入口chunk的文件名称

new MiniCssExtractPlugin({
    // contenthash 只要文件内容没有改变,hash就不会改变
    // 这样如果只有js文件改变了,css没有变化,那么contenthash就不会变,那么浏览器就会继续使用css的缓存文件
    filename: 'css/[name][contenthash:5].css'
}),

https://juejin.cn/post/7026742035667222559#filename

ignoreOrder

类型boolean

默认: false

好像是 来告诉它忽略 CSS 文件的顺序问题,没遇到过这个问题

chunkFilename

只针对非入口chunk生成的文件

类型chunkFilename?: string | ((pathData: PathData, assetInfo?: AssetInfo) => string)

默认: [模块id].css

决定非入口chunk文件的名称

比如有一个css文件是延迟加载进来的

import('./style/index.css');

这样生成的chunk就不是从entry生成的chunk,这个chunkFilename就有作用了

{
    plugins: [
        new MiniCssExtractPlugin({
            // 此选项决定了输出的每个 CSS 文件的名称。根据entry中的入口名称
            filename: 'css/[name].css',
            // 决定非入口chunk文件的名称
            chunkFilename: 'css/chunk_[contenthash:5].css'
        })
    ]
}

就会在打包后的目录生成css/chunk_[contenthash:5].css的文件,contenthash由内容决定

insert

只针对非入口chunk生成的文件

类型: insert?: string | ((linkTag: HTMLLinkElement) => void) | undefined;

默认: document.head.appendChild(linkTag)

只对非入口chunk文件的插入方式

import(/* webpackChunkName: "aCss" */ './style/a.css');
insert: '.title', // 会插入到 .title 类名元素的后边
// insert: function(link) {
//     console.log(1111, link);
//     link.setAttribute('nonce', '1234');
//     document.head.append(link);
// }
attributes

只针对非入口chunk生成的文件

类型: attributes?: Record<string, string> | undefined;

默认: {}

非入口chunk文件的link标签上添加属性

import(/* webpackChunkName: "aCss" */ './style/a.css');
new MiniCssExtractPlugin({
    // ...
    // 非入口chunk文件的link标签上添加属性
    attributes: {
        nonce: '1234'
    }
    // ...
})

会在link标签上添加 nonce="1234"的属性

linkType

好像还是针对非入口chunk文件的

类型: linkType?: string | false | undefined;

默认: text/css

设置link标签的type,好像还是针对非入口chunk文件的

runtime

类型: boolean

默认: true

允许开启/禁用 runtime 生成

不是明白,当设置成false时,异步加载的css就不会自动添加到html中了,需要手动引入执行,一般为true就行

当设置为false时,异步加载的css文件,不能自动的加入到html中,需要借助assets-webpack-plugin插件拿到所有的chunk路径

// index.js

import './style/index.css';
// import(/* webpackChunkName: "indexCss" */ './style/index.css');
import(/* webpackChunkName: "aCss" */ './style/a.css'); // 异步加载不能自动插入到html中
// webpack.config.js

import AssetsWebpackPlugin from 'assets-webpack-plugin';
// ...
plugins: [
    new MiniCssExtractPlugin({
		// ...
        // 允许开启/禁用 runtime 生成
        // 默认为 true
        // 不是明白,当设置成false时,异步加载的css就不会自动添加到html中了,需要手动引入执行,一般为true就行
        runtime: true
    }),
    new AssetsWebpackPlugin({
        filename: 'assets.json', // 输出的文件名
        fullPath: false, // 是否显示完整路径
        removeFullPathAutoPrefix: true // 去除路径的前缀
    })
]

会生成一个assets.json文件

{
    "index": {
        "js": "js/index.js",
        "css": "css/index.e5640.css"
    },
    "aCss": {
        "css": "css/aCss.deddc.css",
        "js": "js/aCss.593a4.js"
    },
    "barJs": {
        "js": "js/barJs.29fdc.js"
    },
    "": {
        "html": "index.html",
        "js": "js/967.80fcc.js"
    }
}

然后再需要插入css的js文件中插入css,先引入json文件,在找到需要插入的css文件路径,然后dom操作

// index.js

import('../assets.json').then(res => {
    console.log(res);
    const link = document.createElement('link');
    link.href = res.aCss.css;
    link.rel = 'stylesheet';
    link.type = 'text/css';
    document.head.append(link);
})
experimentalUseImportModule

类型: boolean

默认: undefined

不懂

`>= 5.52.0` 版本的 webpack 不需要该配置因为该配置项默认启用
 `>= 5.33.2 & <= 5.52.0` 版本的 webpack 需要该配置
对于 `<= 5.33.2` 版本的 webpack 不可用/不安全
常用
new MiniCssExtractPlugin({
    // 此选项决定了输出的每个 CSS 文件的名称。根据entry中的入口名称
    filename: 'css/[name].[contenthash:5].css',
    // 决定非入口chunk文件的名称
    chunkFilename: 'css/[name].[contenthash:5].css',
})

loader

publicPath

类型: {String|Function}

默认: webpackOptions.output.publicPath

为图片、文件等外部资源指定一个自定义的公共路径。

background: url('../../../../static/mouse_cat_1.png');
/* 打包后:*/
background: url('aa/bb/cc8ea85193da95e617826a.png');
emit

类型: Boolean

默认: true

如果设为 false,插件将会提取 CSS 但不会 生成文件

esModule

类型: Boolean

默认: true

将会生成使用 ES 模块语法的 JS 模块,设为false启用commonjs语法

常用
rules: [
    {
        test: /.css$/,
        exclude: /node_modules/,
        use: [
            // 生产环境下使用 mini-css-extract-plugin,开发环境下使用 style-loader
            isProduction ? MiniCssExtractPlugin.loader : styleLoader,
            {
                loader: 'css-loader',
                options: {
                    sourceMap: true
                }
            }
        ]
    }
]

css-minimizer-webpack-plugin

将css文件压缩优化

安装: npm i -D css-minimizer-webpack-plugin

文档上的写法

import CssMinimizerWebpackPlugin from 'css-minimizer-webpack-plugin';
// ...
optimization: {
    minimizer: [
        // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
        `...`,
        // 生产模式下 压缩css
        // TODO 很奇怪,在文档上看是要写在这儿的,但是开发模式下即使minimize: true,也不生效,写在plugin中就没有这个问题
        // new CssMinimizerWebpackPlugin(),
    ],
    // !坑,如果需要开发模式下也压缩,在设置这个属性,否则不要乱设置,可能会造成压缩失效
    minimize: true, // true 开发模式下也压缩
}

发现上面的写法只会对生产模式下的css压缩,下面的写法就可以在生产和开发模式下都压缩了

plugins: [
    // ...
    // 压缩css
    new CssMinimizerWebpackPlugin(),
],
optimization: {
    // ...
    // !坑,如果需要开发模式下也压缩,在设置这个属性,否则不要乱设置,可能会造成压缩失效
    minimize: true, // true 开发模式下也压缩
}

style-resources-loader

导入一些公共的样式文件,比如:variables / mixins / functions,避免在每个样式文件中手动的@import导入

全部走一遍

初始化项目

  1. 初始化npm: npm init -y

  2. 修改npm镜像: 在根目录新建.npmrc文件,文件内容为: registry = "https://registry.npmmirror.com"

  3. 创建.gitignore文件,忽略git要提交的文件,添加内容

node_modules
dist
  1. 安装构建工具: npm i -D webpack webpack-cli

  2. 安装设置环境变量的包:npm i -D cross-env

  3. package.json的依赖

  "devDependencies": {
    "cross-env": "^7.0.3",
    "webpack": "^5.88.0",
    "webpack-cli": "^5.1.4"
  }
  1. 创建目录src/index.js,内容为
console.log('index.js~~~~~');
  1. 在根目录,创建webpack.config.js文件,简单配置一下
const path = require('path');

const isDev = process.env.NODE_ENV === 'development';
console.log('当前环境是:', isDev ? 'development' : 'production');

/** @type {import('webpack').Configuration} */
module.exports = {
    mode: isDev ? 'development' : 'production',
    entry: './src/index.js',
    output: {
        filename: 'index.js',
        path: path.resolve(__dirname, './dist'),
        clean: true,
    }
};
  1. 修改package.json中的scripts
  "scripts": {
    "build": "cross-env NODE_ENV=production webpack --progress"
  },
  1. 然后npm run build,应该是会生成一个目录dist/index.js,内容与src/index.js的内容一致,这样项目就初始换成功了。

添加html

既然是要处理css,得先有html,不然无法查看css是否处理成功了

  1. 安装处理html的插件: npm i -D html-webpack-plugin

  2. 创建文件src/index.html,内容为

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
    <ul>
        <li>钱难挣!!</li>
        <li>屎难吃!!</li>
    </ul>
</body>
</html>
  1. webpack.config.js中添加plugins
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const isDev = process.env.NODE_ENV === 'development';
console.log('当前环境是:', isDev ? 'development' : 'production');

module.exports = {
    plugins: [
        new HtmlWebpackPlugin(
            /** @type {HtmlWebpackPlugin.Options} */{
                title: isDev ? '开发' : '生产',
                filename: 'index.html',
                template: path.resolve(__dirname, './src/index.html'),
                favicon: path.resolve(__dirname, './static/mouse_cat_1.png'),
            }
        )
    ]
}
  1. 运行npm run build,会在dist文件中生成一个index.html文件,浏览器直接打开,查看是否正确

添加样式

  1. 安装包: npm i -D style-loader css-loader less-loader mini-css-extract-plugin

  2. 修改webpack.config.js style-loader将css-loader处理后的css以style标签插入到html中 css-loader将css处理成commonjs,方便style-loader用js将css插入到html中 less-loader将less处理成css mini-css-extract-plugins是将css抽离成单独的文件

// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// ...
// ...
const isDev = process.env.NODE_ENV === 'development';
console.log('当前环境是:', isDev ? 'development' : 'production');
// 普通的 css-loader
const cssLoader = {
    loader: 'css-loader',
    options: {sourceMap: true}
}
// 模块化的 css-loader
const cssModuleLoader = {
    loader: 'css-loader',
    options: {
        sourceMap: true,
        modules: {
            mode: 'local', // 所有的css文件,开启局部作用域
            localIdentName: isDev ? '[path]_[name]_[local]_[hash:base64:5]' : '[hash:base64]',
            exportLocalsConvention: 'camelCase'
        }
    }
}
module.exports = {
    // ...
	module: {
        rules: [
            // css文件
            {
                test: /\.css$/,
                exclude: /node_modules/,
                oneOf: [
                    // 带有global字样的css就不开启模块化,less同理
                    {
                        test: /\\.global\\.css$/,
                        use: [
                            isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
                            cssLoader,
                        ]
                    },
                    {
                        use: [
                            isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
                            cssModuleLoader,
                        ]
                    }
                ]
            },
            // less文件
            {
                test: /\.less$/,
                exclude: /node_modules/,
                oneOf: [
                    {
                        test: /\.global\.less/,
                        use: [
                            isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
                            cssLoader,
                            {loader: 'less-loader', options: {sourceMap: true}},
                        ]
                    },
                    {
                        use: [
                            isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
                            cssModuleLoader,
                            {loader: 'less-loader', options: {sourceMap: true}},
                        ]
                    }
                ]
            }
        ]
    },
    plugins: [
        // ...
        // 抽离css
        new MiniCssExtractPlugin(
            /** @type {MiniCssExtractPlugin.PluginOptions} */{
                // 此选项决定了输出的每个 CSS 文件的名称。根据entry中的入口名称
                filename: 'css/[name].[contenthash:5].css',
                // 决定非入口chunk文件的名称
                chunkFilename: 'css/[name].[contenthash:5].css'
            }
        )
    ]
}
  1. 创建文件src/css/index.global.css src/css/index.css src/less/index.global.less src/less/index.css 随便写点样式看看生不生效就行
/* index.global.css */
* {
    box-sizing: border-box;
}
.flex {
    display: flex;
}
/* index.css */
.list {
    border: 2px solid black;
    list-style: none;
}
/* index.global.less */
.p-20 {
    padding: 20px;
}
.w-50 {
    width: 50%;
}
/* index.less */
.ul-list {
    min-height: 100px;
    align-items: center;
    :global {
        .li-item {
            color: red;
        }
    }
}

样式文件,引入入口文件index.js并使用模块化类名,引入才会被打包

// index.js
import './css/index.global.css';
import cssStyle from './css/index.css';

import './less/index.global.less';
import lessStyle from './less/index.less';
{
    console.log('cssStyle', cssStyle);
    console.log('lessStyle', lessStyle);
    const list = document.querySelector('.list');
    list.classList.add(cssStyle.list);
    list.classList.add(lessStyle['ul-list']);
}
console.log('index.js~~~~~');

html添加类名,css才会起作用

<body>
    <ul class="list flex justify-center color-red w-50 p-20">
        <li class="mr-18 li-item">钱难挣!!</li>
        <li class="mr-18 li-item">屎难吃!!</li>
    </ul>
</body>

修改package.json

  "scripts": {
    "build": "cross-env NODE_ENV=production webpack --progress",
    "build:dev": "cross-env NODE_ENV=development webpack --progress"
  },
  1. npm run build或者npm run build:dev各自查看,处理后的样式是否生效,模块化下的类名是否有区别

优化样式

优化样式就是将css压缩、自动添加前缀、使用css新特性等,需要借助postcss的功能

  1. 安装: npm i -D postcss-loader postcss

  2. 修改webpack配置,在css-loader之后使用postcss-loader,如果css-loader使用了模块化,还需要配置importLoaders

// webpack.config.js
const cssModuleLoader = {
    loader: 'css-loader',
    options: {
        sourceMap: true,
        modules: {
            mode: 'local',
            localIdentName: isDev ? '[path]_[name]_[local]_[hash:base64:5]' : '[hash:base64]',
            exportLocalsConvention: 'camelCase'
        },
        // 加上这个属性
        importLoaders: 2, // 模块化才需要配置,在css-loader之前,允许有几个loader
    }
}
const postcssLoader = {
    loader: 'postcss-loader',
    options: {
        postcssOptions: {
            plugins: [

            ]
        }
    }
}
module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                exclude: /node_modules/,
                oneOf: [
                    {
                        test: /\\.global\\.css$/,
                        use: [
                            isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
                            cssLoader,
                            postcssLoader
                        ]
                    },
                    {
                        use: [
                            isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
                            cssModuleLoader,
                            postcssLoader
                        ]
                    }
                ]
            },
            {
                test: /\.less$/,
                exclude: /node_modules/,
                oneOf: [
                    {
                        test: /\.global\.less/,
                        use: [
                            isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
                            cssLoader,
                            postcssLoader,
                            {loader: 'less-loader', options: {sourceMap: true}},
                        ]
                    },
                    {
                        use: [
                            isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
                            cssModuleLoader,
                            postcssLoader,
                            {loader: 'less-loader', options: {sourceMap: true}},
                        ]
                    }
                ]
            }
        ]
    }
}
  1. 设置浏览器兼容度
// package.json
{
  // ...
  "browserslist": [
    "> 0.1%",
    "not dead"
  ]
}
  1. 使用css的新特性: npm i -D postcss-preset-env,其内已经包含了autoprefixer的功能 修改上面的第二部中的postcssLoader变量

    // webpack.config.js
    const postcssLoader = {
        loader: 'postcss-loader',
        options: {
            sourceMap: true,
            postcssOptions: {
                plugins: [
                    'postcss-preset-env', // 已经包含了autoprefixer
                    // 'cssnano' // 在这儿使用cssnano,也可以使用css-minimizer-webpack-plugin,更偏向css-minimizer-webpack-plugin,因为他跟webpack结合的更好
                ]
            }
        }
    }
  2. 压缩css: npm i -D css-minimizer-webpack-plugin,内部默认也是使用的cssnano压缩的,但是跟webpack结合的更好

    // webpack.config.js
    const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin');
    module.exports = {
        // ...
    	optimization: {
            minimizer: [
                // `...`,
                // 压缩css
                new CssMinimizerWebpackPlugin({
                    // parallel: true, // 默认就是开启的 多进程并发执行,os.cpus().length - 1
                })
            ]
        }
    }

校验样式

  1. 借助stylelint,在webpack中借助stylelint-webpack-plugin插件 npm i -D stylelint stylelint-webpack-plugin

  2. 安装vscode插件: vscode-stylelint,用于在编辑器上直接显示css语法错误,并且设置vscode配置文件 在项目中创建.vscode/settings.json,设置css校验的插件

    {
        "[css]": {
            "editor.defaultFormatter": "stylelint.vscode-stylelint"
        },
    }
  3. 安装stylelint推荐的配置: npm i -D stylelint-config-standard,已经配置好了许多规则

  4. 根目录创建文件stylelint.config.js

    // stylelint.config.js
    /** @type {import('stylelint').Config} */
    module.exports = {
        extends: ['stylelint-config-standard'],
        overrides: [
            {
                files: 'src/**/*.css',
            }
        ]
    }
  5. 配置webpack.config.js

    // webpack.config.js
    const StylelintWebpackPlugin = require('stylelint-webpack-plugin');
    module.exports = {
        rules: [
    		new StylelintWebpackPlugin({
                files: ['src/**/*.css'],
                threads: true, // 开启多线程
                // 插件内部已经默认开启了缓存,可以不用设置
                // cache: true,
                // cacheLocation: 'node_modules/.cache/stylelint-webpack-plugin/.stylelintcache',
            })
        ]
    }
  6. 修改css,然后npm run build,查看是否会校验错误,并终止编译

    .list {
        
        border: 2px solid black;
        list-style: none;
    }
  7. 校验less文件也是类似的,修改如下文件,并且安装npm i -D postcss-less

    stylelint.config.js

    /** @type {import('stylelint').Config} */
    module.exports = {
        extends: ['stylelint-config-standard'],
        overrides: [
            {
                files: 'src/**/*.css',
            },
            {
                files: 'src/**/*.less',
                customSyntax: 'postcss-less'
            }
        ],
        rules: {
            // 不允许未知的伪类选择器
            'selector-pseudo-class-no-unknown': [true, {
                ignorePseudoClasses: ['global']
            }]
        }
    }

    webpack.config.js中的StylelintWebpackPlugin插件

    // webpack.config.js
    new StylelintWebpackPlugin({
        files: ['src/**/*.{css,less}'],
        threads: true, // 开启多线程
        // 插件内部已经默认开启了缓存,可以不用设置
        // cache: true,
        // cacheLocation: 'node_modules/.cache/stylelint-webpack-plugin/.stylelintcache',
    })

使用prettier修复错误

  1. 安装vscode插件: prettier-vscode

  2. prettier配合stylelint一起校验,安装: npm i -D stylelint-prettier

  3. 配置stylelint.config.js,添加plugins和rules

    {
    	plugins: ['stylelint-prettier'],
        rules: {
            'prettier/prettier': [true, {
                'tabWidth': 4, // 缩进 4
                'useTabs': false, // false 缩进使用空格,true使用制表符
                'singleQuote': false, // 使用单引号代替双引号
                'semi': true, // 在语句末尾打印分号
                'printWidth': 120, // 指定每行的最大列数
                'endOfLine': 'lf', // 设置行尾符号类型
            }]
        }
    }
  4. npm run build,检查样式是否有错误

提交代码校验

详见上面的git提交校验

处理资源文件

  • 文档: https://www.webpackjs.com/guides/asset-modules/

  • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。

  • asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。

  • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。

  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。

除了第三步外,其他步骤都是在配置项目,直接看第三步也可以

  1. 初始化项目,就不多说了

    • npm init -y

    • 设置.npmrc,更改镜像

    • 设置 .gitignore 忽略一些不用提交的文件,比如node_modules、dist、.DS_Store等文件

    • 安装npm i -D webpack webpack-cli

    • 创建入口文件src/index.js

    • 创建并配置 webpack.config.js,简单配置下即可,先让项目可以跑起来

      const webpack = require('webpack');
      const path = require('path');
      
      /** @type {webpack.Configuration} */
      module.exports = {
          entry: './src/index.js',
          output: {
              filename: 'index.js',
              path: path.resolve(__dirname, 'dist'),
              clean: true
          }
      }
    • 修改package.json,方便命令启动,然后启动命令,查看打包是否成功

  2. 处理一些资源比如图片、字体等,需要在浏览器上看效果,因此还需要处理并打包生成html文件

    • 安装: npm i -D html-webpack-plugin

    • 创建文件src/index.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
      </head>
      <body>
          <img src="./static/mouse_cat_1.png" alt="猫猫和鼠鼠鸭">
      </body>
      </html>
    • 修改webpack.config.js

      const HtmlWebpackPlugin = require('html-webpack-plugin');
      module.exports = {
      	plugins: [
              new HtmlWebpackPlugin({
                  template: './src/index.html',
                  templateParameters: {
                      title: isDev ? '开发' : '生产',
                  },
                  filename: 'index.html',
              })
          ]
      }
    • 打包后发现图片显示的路径不正确,需要使用html-loader来处理html中的图片资源路径,再次打包,就好了

    • 现在打包图片,会发现,图片被处理到dist目录下

  3. 使用webpack5中内置的asset module模块,处理图片资源,配置webpack.config.js

    module.exports = {
    	module: {
            rules: [
                {
                    test: /\.(png|jpg|jpeg|svg|gif)$/,
                    /* 
                        文档: https://www.webpackjs.com/guides/asset-modules/
                        asset/resource: 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
                        asset/inline: 导出一个资源的 data URI。之前通过使用 url-loader 实现。
                        asset: 自动地在 resource 和 inline 之间进行选择:小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。
                        asset/source: 导出资源的源代码。
                    */
                    type: 'asset',
                    generator: {
                        // 打包后 文件路径前拼接的前缀,适合用来拼接 网络地址
                        // publicPath: 'https://cdn/assets/',
                        // 指定打包后,文件资源存放的文件夹'
                        // outputPath: 'assets',
                        // 文件名中 加上文件路径,也可以设置文件保存的位置,适合 本地的相对路径
                        filename: 'img/[name].[hash:5][ext][query]',
                    },
                    parser: {
                        /* 
                            优点:减少请求次数
                            缺点:增大文件体积
                            因此 才判断 小文件,处理成base64,大文件,还是请求吧
                        */
                        // 处理文件时,文件小于多少,自动处理成 base64 字符串
                        dataUrlCondition: {
                            maxSize: 200 * 1024 // 默认8kb
                        }
                        // 函数写法
                        // dataUrlCondition(source, { filename, module }) {
                        //     // console.log(1111, source.toString());
                        //     // console.log(2222, filename);
                        //     const ext = extname(filename)
                        //     var buff = fs.readFileSync(module.resourceResolveData.path);
                        //     // console.log(2222, source.toString().length, Buffer.byteLength(source), Buffer.byteLength(buff));
                        //     if (ext === '.webp' && Buffer.byteLength(source) > 32 * 1024) {
                        //         return true
                        //     }
                        //     return false;
                        // }
                    }
                },
                // 字体图标 以及其他的资源,原封不动的 输出
                {
                    test: /\\.(eot|ttf|woff2?|pdf|mp3|mp4|docx|txt)$/,
                    // 原封不动的 插入中 不转成 base64
                    type: 'asset/resource',
                    generator: {
                        filename: 'file/[name].[hash:5][ext][query]',
                    }
                },
            ]
        },
    }

    打包后,图片就会被保存到dist目录下的img文件夹下

    补充:

    可以在 generator.filename 中配置打包后输出的文件名,也可以在 output.assetModuleFilename 统一配置其他资源打包后输出的文件命名

    {
    	output: {
            filename: 'js/index.js',
            path: path.resolve(__dirname, 'dist'),
            clean: true,
            assetModuleFilename: 'asset/[name].[hash:5][ext][query]'
        },
    }
  4. js中的图片和css中的图片都可以正常的处理

    // index.js
    import imgSrc from './static/mouse_cat_1.png';
    console.log('hello world');
    {
        const html = `<div><img src="${imgSrc}" alt="" /></div>`;
        document.body.innerHTML += html;
    }

    默认已经配置好了可以处理css文件,需要style-loader、css-loader)

    /* index.css */
    * {
        box-sizing: border-box;
    }
    img {
        font-size: 0;
        vertical-align: top;
        padding: 10px;
    }
    .bg {
        width: 100px;
        height: 100px;
        padding: 10px;
        background: url("../static/mouse_cat_1.png") no-repeat 0 0/contain;
        background-clip: content-box;
    }

处理js

初始化项目,应该已经很熟练了

  • 初始化npm:npm init -y

  • 创建 .npmrc 修改内容:registry="https://registry.npmmirror.com"

  • 创建 .gitignore 添加git需要忽略掉的文件

  • 创建 webpack.config.js 添加webpack配置项

  • 安装 webpack、webpack-cli、cross-env: npm i -D webpack webpack-cli cross-env html-webpack-plugin 修改package.json

      "scripts": {
        "build:dev": "rm -rf dist && cross-env NODE_ENV=development webpack --progress",
        "build": "rm -rf dist && cross-env NODE_ENV=production webpack --progress"
      },

    修改webpack.config.js

    const webpack = require('webpack');
    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    const isProd = process.env.NODE_ENV === 'production';
    /** @type {webpack.Configuration}  */
    module.exports = {
        mode: isProd ? 'production' : 'development', // 模式
        entry: './src/index.js', // 入口文件
        output: {
            filename: 'js/index.js',
            path: path.resolve(__dirname, 'dist'),
            clean: true,
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: './src/index.html',
                templateParameters: {
                    title: isProd ? '生产' : '开发'
                }
            })
        ]
    }

    npm run build,生成的html文件,打开查看是否正确

babel-loader

webpack本身就可以直接处理 js和json文件,但是写代码的时候,并不能毫无忌惮的使用最新的语法,因为他可能在有些平台上不兼容,因此就需要babel来帮助你,将最新的语法,转换成可以在比较旧版本的平台上运行的代码。

比如在index.js中有一段代码

// index.js
const add = (x = 1, y = 2) => x + y;
const res = add();
console.log(res);

打包后dist/js/index.js目录

!function(){const o=((o=1,n=2)=>o+n)();console.log(o)}();

可以看到,几乎是原封不动的输出,这样如果需要在低版本的平台运行的话,可能有的平台就不支持形参的默认值,这时,就需要用到babel了。

babel的详细笔记可以看另一篇文章笔记

转语法

  1. 安装: npm i -D babel-loader @babel/core @babel/preset-env

  2. 既然是要兼容低版本的平台,那肯定是要配置 browserslist 的,在哪配置都行 package.json 或者 .browserslistrc 或者 babel.config.js 或者 webpack.config.js

    // package.json
    {
          "browserslist": [
            "> 0.02%",
            "not dead"
          ]
    }
  3. 配置webpack.config.js

    // webpack.config.js
    module.exports = {
        module: {
            rules: [
                {
                    test: /\.m?js$/,
                    exclude: /node_modules/,
                    use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                // 只转换 语法,比如箭头函数、... 扩展,不会转换新增的api 比如 Object.assign等等
                                presets: ['@babel/preset-env']
                            }
                        }
                    ]
                }
            ]
        },
    }

    然后再打包,发现函数形参被转换成了arguments来兼容低版本的浏览器

    // 浏览器上复制的
    !function() {
        var n = function() {
            return (arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : 1) + (arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 2)
        }();
        console.log(n)
    }();
  4. 上面通过@babel/preset-env预设,将一些比较新的语法转换成低版本平台也可以识别运行的代码,但是并不能转换一些新增的api,比如Object.assign、Reflect、Promise等,这就需要借助一些polyfill,来帮助实现这些功能。

引入辅助函数

辅助函数的作用就是 @babel/preset-env 在转换语法的时候,需要用到一些函数帮助转换语法,而正常情况下,他会在每一个文件中都注入这些辅助函数,但是如果两个文件中都使用了相同的语法,这样就会在两个文件中都重复的注入这些辅助函数,因此要想办法,将辅助函数改为外界引入的方式,所有文件都改为引入这些辅助函数,这样可以大大减少代码量。

需要借助到 @babel/runtime@babel/plugin-transform-runtime 插件

  • @babel/runtime 中包含了 转换语法时,用到的辅助函数,安装 @babel/preset-env 的时候就带有了,但是还是推荐再安装一下,改为为生产依赖。

  • @babel/plugin-transform-runtime 帮助将注入到每个文件的辅助函数改为从 @babel/runtime 引入的模式

现在更改 index.js 中的内容

// index.js
import './aa.js';
class Preson {
    static name = '张三';
}
console.log(Preson.name);
// aa.js
class Animal {
    static name = '鼠鼠鸭';
}
console.log(Animal.name);
  1. 安装: npm i @babel/runtime npm i -D @babel/plugin-transform-runtime

  2. 配置 webpack.config.js

    // webpack.config.js
    {
        loader: 'babel-loader',
        options: {
            presets: [
                // 只转换 语法,比如箭头函数、... 扩展,不会转换新增的api 比如 Object.assign等等
                ['@babel/preset-env', {
                    // entry 时,会将会 core-js 中所有的 polyfill 引入,
                    // 并且需要在入口文件处手动的引入 corejs, import 'core-js';
                    // "useBuiltIns": "entry",
    
                    // usage 时,会自动的按需加载 polyfill,也不需要在入口文件处手动的引入 import 'core-js'
                    useBuiltIns: "usage",
                    corejs: '3.31.1'
                }]
            ],
            plugins: [
                '@babel/plugin-transform-runtime'
            ]
        }
    }
  3. 在此打包,首先发现代码量少了,然后在浏览器上查看源码发现,确实没有重复的函数注入了,要问为什么在浏览器上看,因为webpack打包后的代码在编辑器上实在是不好看,除非使用@babel/cli打包的,看起来要比webpack看起来要清晰的多。

polyfill

这两种方式不能混用,选其一即可

方式1

这种方法一般就是用在项目开发的时候,但是可能会有污染全局windows对象上的属性的风险。

  1. 因为要测试是否添加polyfill是否成功,要使用低版本的浏览器,不太好整,这里取巧一下,在html中,将Object.entries 函数设置为undefined,这样打包之后,浏览器就会报错,没有这个函数

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title><%= title %></title>
        <script>
            window.Object.entries = undefined;
        </script>
    </head>
    // index.js
    const obj = {name: '张三', age: 18};
    console.log(Object.entries(obj));
  2. 接下来使用polyfill,先安装npm i core-js,有全部引入和按需引入两种方法,选其中一种就行,一般就是按需引入了

    core-js 可以使用版本2或3,分别是

    npm install core-js@3 --save

    # or

    npm install core-js@2 --save

    • 借助@babel/preset-env中的 useBuiltInscorejs 属性,可以完成polyfill的引入

      • useBuiltIns: 'entry' 时,会将会 core-js 中所有的 polyfill 引入,并且需要在入口文件处手动的引入 import 'core-js';
      • useBuiltIns: 'usage' 时,会自动的按需加载 polyfill,不需要在入口文件处手动的引入 import 'core-js'
      • corejs: 'xx',需要配合useBuiltIns使用才生效,安装的core-js是哪个版本,就写哪个版本号
    • 全部引入

      // webpack.config.js
      modules.export = {
      	module: {
              rules: [
                  {
                      test: /\.m?js$/,
                      exclude: /node_modules/,
                      use: [
                          {
                              loader: 'babel-loader',
                              options: {
                                  presets: [
                                      ['@babel/preset-env', {
                                          "useBuiltIns": "entry",
                                          corejs: '3.31.1' // core-js 安装的版本
                                      }]
                                  ]
                              }
                          }
                      ]
                  }
              ]
          },
      }
      // index.js 入口文件的顶部
      import 'core-js'`;
    • 按需引入,修改useBuiltIns参数即可,也不需要在index.js中引入core-js了

      {
          loader: 'babel-loader',
          options: {
              presets: [
                  ['@babel/preset-env', {
                      "useBuiltIns": "usage", // 按需
                      corejs: '3.31.1'
                  }]
              ]
          }
      }
  3. 然后打包,发现原本在html中将entries函数设置为undefined,导致在浏览器上运行报错的现象消失了,大概率就是core-js帮助我们,重新再Object上添加了entries函数。

方式2

这种方式一般用在第三方库中,避免污染全局属性和方法,也比较好用

  1. 借助了 @babel/plugin-transform-runtime 的能力,配合 @babel/runtime-corejs3 ,实现 polyfill 的能力。

  2. @babel/runtime-corejs3 插件已经包含了上面 @babel/runtimecore-js 的能力,因此需要先卸载掉 npm uni @babel/runtime core-js,并且删掉@babel/preset-env关于polyfill的配置。

  3. 安装: npm i @babel/runtime-corejs3

  4. 然后配置webpack.config.js

    module.exports = {
    	module: {
            rules: [
                {
                    test: /\.m?js$/,
                    exclude: /node_modules/,
                    use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                presets: [
                                    '@babel/preset-env'
                                ],
                                plugins: [
                                    ['@babel/plugin-transform-runtime', {
                                        corejs: 3 // 安装的 @babel/runtime-corejs3 版本,2 or 3
                                    }]
                                ]
                            }
                        }
                    ]
                }
            ]
        },
    }

    打包即可

常用配置

在项目中如下,如果是第三方库,推荐使用上面的方式2 polyfill 方法

// webpack.config.js
modules.export = {
	module: {
        rules: [
            {
                test: /\.m?js$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            cacheDirectory: true, // 开启缓存
                            cacheCompression: false, // 关闭 压缩缓存
                            presets: [
                                ['@babel/preset-env', {
                                    useBuiltIns: 'usage',
                                    corejs: '3.31.1'
                                }]
                            ],
                            plugins: [
                                '@babel/plugin-transform-runtime'
                            ]
                        }
                    },
                ]
            }
        ]
    },
}

eslint

检查js语法

在webpack中需要用到 eslint-webpack-plugin 插件

安装: npm i -D eslint-webpack-plugin eslint

配置文件

优先顺序

  • .eslintrc.js
  • .eslintrc.cjs
  • .eslintrc.yaml
  • .eslintrc.yml
  • .eslintrc.json
  • package.json

配置如下 也可以 npx eslint --init,生成配置文件

// 安装 npm i -D eslint-define-config 可以有语法提示
const {defineConfig} = require('eslint-define-config');
module.exports = defineConfig({
    root: true,
    // 继承一些预设的规则
    extends: ['eslint:recommended'],
    // 指定环境 
    // https://eslint.nodejs.cn/docs/latest/use/configure/language-options#%E6%8C%87%E5%AE%9A%E7%8E%AF%E5%A2%83
    env: {
        es6: true, // es6中的全局变量
        browser: true, // 浏览器全局变量
    },
    // 指定全局变量
    // https://eslint.nodejs.cn/docs/latest/use/configure/language-options#%E6%8C%87%E5%AE%9A%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F
    globals: {
        qxy: 'writable', // 允许覆盖
        qqq: 'readonly', // 不允许覆盖
    },
    // 指定解析器选项
    // https://eslint.nodejs.cn/docs/latest/use/configure/language-options#%E6%8C%87%E5%AE%9A%E8%A7%A3%E6%9E%90%E5%99%A8%E9%80%89%E9%A1%B9
    parserOptions: {
        ecmaVersion: 'latest', // 最新的js版本
        sourceType: 'module', // 是否是 模块化
        ecmaFeatures: {
            'jsx': true
        }
    },
    // 扩展 ESLint
    plugins: [],
    // 定制一些规则
    // https://eslint.nodejs.cn/docs/latest/rules/
    rules: {
        'no-console': ['warn', {
            allow: ['error', 'warn', 'dir'],
        }],
        // 不允许有空块
        'no-empty': 'warn',
        // 缩进格式
        'indent': ['error', 4],
        // 字符串的引号格式
        'quotes': ['error', 'single'],
        // 是否使用分号
        'semi': ['error', 'always'],
        // 不允许使用var
        'no-var': 'error',
        // 函数左括号 是否有空格
        'space-before-function-paren': ['error', {
            anonymous: 'never', // 匿名函数
            named: 'never', // 具名函数
            asyncArrow: 'always' // async 函数
        }],
        'no-multiple-empty-lines': ['error', {
            max: 2, // 最大空行
            maxBOF: 0, // 文件开头最大空行
            maxEOF: 1, // 文件末尾最大空行
        }],
        // 不允许多个空格
        'no-multi-spaces': 'error',
        // 不允许在行尾尾随空格
        'no-trailing-spaces': ['error', {
            skipBlankLines: false,
            ignoreComments: true, // 允许在注释块中使用尾随空格
        }],
        // 在分号前后强制执行一致的间距
        'semi-spacing': ['error', {
            before: false,
            after: true,
        }]
    },

    // 忽略的文件 或者在 .eslintignore 中配置
    ignorePatterns: ['node_modules', 'dist'],
    // 覆盖
    // https://eslint.nodejs.cn/docs/latest/use/configure/configuration-files#%E8%A6%86%E7%9B%96%E5%A6%82%E4%BD%95%E5%B7%A5%E4%BD%9C
    overrides: [
        {
            // 只能写在 这儿,写在外面校验就失效了
            files: [
                '.eslintrc.{js,cjs}',
                'webpack.config.js'
            ],
            env: {
                node: true // Node.js 全局变量和 Node.js 作用域。
            }
        }
    ],
});

配置vscode

vscode上安装eslint插件,并且在项目根目录创建 .vscode/settings.json,保存自动格式化

{
    // 推荐笔记:https://juejin.cn/post/6844904068746313736

    // "eslint.alwaysShowStatus": true 已废弃
    // "eslint.autoFixOnSave": false, // 已废弃,被下面替代

    // 保存时 格式化代码
    "editor.codeActionsOnSave": {
        "source.fixAll": false,
        "source.fixAll.eslint": true // 开启 eslint 自动格式化
    },
    // 控制是否使用 eslint
    "eslint.enable": true,
    // 不显示eslint校验的警告信息
    "eslint.quiet": false
}

配置webpack

webpack.config.js 中配置 eslint-webpack-plugin ,在编译的时候,可以校验错误

const EslintWebpackPlugin = require('eslint-webpack-plugin');
module.exports = {
    plugins: [
		new EslintWebpackPlugin({
            context: path.resolve(__dirname, './src'),
            files: ['**/*.{js,ts,jsx,tsx}'], // 指定要校验的目录、文件或 globs ,必须是相对于 options.context 的相对路径
            // cache: true, // 默认就是 true
            // cacheLocation: 'node_modules/.cache/eslint-webpack-plugin/.eslintcache', // 默认就是这个值

            // TODO 开启多线程 导致eslint校验失效,
            // issues回答 还没有解决: https://github.com/webpack-contrib/eslint-webpack-plugin/issues/146
            // threads: true, // 开启多线程 默认false 
        })
    ]
}

修改js文件,打包,查看是否可以校验错误

git提交校验

在这不多描述,可以看css中的设置git提交检验

按照步骤来,完美运行,需要将stylelint的命令校验,改为eslint的就行。

优化

  • 使用 thread-loader 开启多进程,适用于项目构建开销比较大的时候 文档:https://webpack.js.org/loaders/thread-loader/

    {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: [
            {
                // 文档: https://webpack.js.org/loaders/thread-loader/
                // 适用于 项目构建开销比较大的情况
                loader: 'thread-loader',
                options: {
                    workers: require('os').cpus().length // 进程数量 默认为 cpu的核数 - 1
                }
            },
            {
                loader: 'babel-loader',
                options: {
                    cacheDirectory: true, // 开启缓存
                    cacheCompression: false, // 关闭 压缩缓存
                }
            },
        ]
    }
  • babel-loader 开启缓存(默认关闭),开启缓存压缩(默认开启) 文档:https://webpack.js.org/loaders/babel-loader/

    {
        loader: 'babel-loader',
        options: {
            cacheDirectory: !isProd, // 默认为false 不开启缓存,开发环境再开启
            cacheCompression: true, // 默认为true 压缩缓存,转移的文件特别多的时候,会有好处
        }
    },
  • eslint-webpack-plugin 开启缓存(默认就是开启的)和多进程(默认false,有bug,开启校验就失效了) 文档:https://webpack.js.org/plugins/eslint-webpack-plugin/

    plugins: [
        new EslintWebpackPlugin({
            context: path.resolve(__dirname, './src'),
            // files: ['src/**/*.js'], // 需要校验的文件
            // cache: true, // 默认就是 true
            // cacheLocation: 'node_modules/.cache/eslint-webpack-plugin/.eslintcache', // 默认就是这个值
    
            // TODO 开启多线程 导致eslint校验失效,
            // issues回答 还没有解决: https://github.com/webpack-contrib/eslint-webpack-plugin/issues/146
            // threads: true, // 开启多核 默认false 
        })
    ],
  • js压缩开启多进程(默认开启),但是是 cpu的核数-1 文档:https://webpack.js.org/plugins/terser-webpack-plugin/

    optimization: {
        minimizer: [
            // eslint-disable-next-line
            `...`,
            new TerserWebpackPlugin({
                parallel: require('os').cpus().length, // 开启多进程 默认并发运行数: os.cpus().length - 1 。
            })
        ]
    }

webpack优化

初始化项目

先初始化项目,不多说了,应该很熟练了。

  • npm init -y, mkdir src, touch webpack.config.js src/index.js index.html .npmrc .gitignore .editorconfig

  • .npmrc 中添加npm的镜像地址 registry="https://registry.npmmirror.com"

  • .gitignoreeditorconfigindex.html 中分别添加

    # .gitignore
    node_modules
    dist
    .DS_Store
    # editorconfig
    [*]
    charset = utf-8
    insert_final_newline = true
    end_of_line = lf
    indent_style = space
    indent_size = 4
    max_line_length = 80
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title><%= title %></title>
    </head>
    <body></body>
    </html>
  • 安装包 npm i -D webpack webpack-cli html-webpack-plugin cross-env,并初始化 webpack.config.js 配置

    // webpack.config.js
    const webpack = require('webpack');
    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    const isDev = process.env.NODE_ENV === 'development';
    
    /** @type {webpack.Configuration} */
    module.exports = {
        mode: isDev ? 'development' : 'production',
        entry: {
            index: './src/index.js'
        },
        output: {
            filename: 'js/[name]_[contenthash:5].js',
            path: path.resolve(__dirname, './dist'),
            clean: true,
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: path.resolve(__dirname, './index.html'),
                templateParameters: {
                    title: isDev ? '开发' : '生产'
                }
            })
        ]
    }
  • 配置 package.json

      "scripts": {
        "build": "cross-env NODE_ENV=production webpack --progress",
        "build:dev": "cross-env NODE_ENV=development webpack --progress"
      },
  • 执行命令 npm run build 查看打包结果是否正常

splitChunks

文档:https://www.webpackjs.com/configuration/optimization/#optimizationsplitchunks

作用:用来分割打包后的文件,将多次引用的文件抽离成单独的文件,将node_modules中引用的文件,抽离成单独的文件。

默认值如下:

基本不用怎么配置,因为默认对 node_modules 进行分割。其他的文件至少被引用2次,而且文件大小得大于20000字节的才会被分割。

只需要把 chunks 的值,改为all,就行,下面为了演示,会故意改一些选项,达到分割的目的。

// webpack.config.js
module.exports = {
        // 代码分割
        splitChunks: {
            chunks: 'async', // 'all' 对所有模块进行分割
            // 下面的是默认配置
            minSize: 20000, // 要生成的块的最小大小(以字节为单位)
            minRemainingSize: 0, // 类似 minSize, 最后确保提取的文件大小不能为0
            minChunks: 1, // 至少被引用的次数,满足条件才会被分割
            maxAsyncRequests: 30, // 按需加载时的最大并行请求数
            maxInitialRequests: 30, // 入口点的最大并行请求数
            enforceSizeThreshold: 50000, // 超过 50kb的 一定会被单独打包(此时会忽略 minRemainingSize maxAsyncRequests maxInitialRequests)
            // 下面 选项中如果 没有设置的属性,会使用上面设置的属性
            cacheGroups: { // 哪些模块要被打包到一组
                // node_modules 引入的文件
                defaultVendors: { // 组名
                    test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
                    priority: -10, // 权重(越大越高)
                    reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出去的模块,则它将被重用,而不是生成新模块
                },
                // 多入口文件,每个入口都引入相同的文件才可能生效
                default: { // 其他没有写的配置会使用 上面的默认值
                    minChunks: 2, // 这里的 minChunks 权重更大
                    priority: -20,
                    reuseExistingChunk: true,
                },
            },
        }
    }
}

多入口文件

  1. 现在有三个文件:index.js about.js utils.js

    // utils.js
    export const add = (...rest) => {
        return rest.reduce((total, item) => {
            return total + item;
        }, 0);
    }
    // index.js
    const {add} = require('./utils');
    console.log('===== index.js =====');
    {
        const x = 1;
        const y = 2;
        const res = add(1, 2);
        console.log(res);
    }
    // about.js
    const {add} = require('./utils');
    console.log('----- about.js -----');
    {
        const x = 'x';
        const y = 'y';
        console.log(add(x, y));
    }
  2. 修改 webpack.config.js 文件为多入口

        entry: {
            index: './src/index.js',
            about: './src/about.js',
        },
  3. 生产模式下,打包后,发现无论 index.js 还是 about.js 中,都被注入了add函数,这样就有些重复了,增大了代码量。

  4. 使用 splitChunks,对多次引用的代码进行分割,单独抽离出来,其他模块共享抽离出来的代码,这样大大的减少了代码量。

  5. 设置 splitChunks,一般来说不需要太精细的设置,直接设置个 chunks: 'all',就可以了。这里只是为了出效果。。。

    // webpack.config.js
    module.exports = {
        optimization: {
            splitChunks: {
                // 多入口情况下,为了看效果,手动改一些配置
                chunks: 'all',
                cacheGroups: {
                    // 多入口文件的情况下才会生效,单入口的时候,没用
                    default: {
                        minSize: 100, // 大于100字节的文件才 分割
                        minChunks: 2,
                        priority: -20,
                        reuseExistingChunk: true,
                    }
                },
            }
        }
    }

    再次在生产环境下打包,发现 utils.js(117bytes) 被分割出来了,index.jsabout.js,中不在有add函数的代码。

  6. ==补充==,在 runtimeChunk 文档中有提示,如果是在同一个页面中有多个入口文件,最好设置为 'single',实测如果在 index.js 文件中,再使用 utils.js 的其他函数(比如 ride),而在 about.js 中没有使用,会导致打包后 index.js 中的的 add 和 ride 函数还是被注入进 index.js 文件中的,并没有被被分割出去,但是加上 runtimeChunk: 'single' 之后,就恢复正常了。 文档地址: https://www.webpackjs.com/configuration/optimization/#optimizationruntimechunk

    module.exports = {
        // ...
    	optimization: {
            runtimeChunk: 'single',
            splitChunks: {
                chunks: 'all',
                cacheGroups: {
                    default: {
                        minSize: 100, // 大于100字节的文件才 分割
                        minChunks: 2,
                        priority: -20,
                        reuseExistingChunk: true,
                    }
                }
            }
        }
    }

单入口文件

其实只用设置个 chunks 即可,其他配置默认就可以,可以将 node_modules 给抽离出来

module.exports = {
    // ...
	optimization: {
        splitChunks: {
            chunks: 'all',
        }
    }
}

比如在项目中引入了lodash,打包后就会将lodasj单独提取成一个文件

// index.js
import {isInteger} from 'lodash';
import {ride, subtract} from './utils';
console.log('===== index.js =====');
{
    const x = 1;
    const y = 2;
    console.log('ride', ride(x, y));
    console.log('subtract', subtract(x, y));
    console.log('isInteger', isInteger(12.999999999999999999999999999)); // 精度爆了
}

runtimeChunk

配置文档:https://www.webpackjs.com/configuration/optimization/#optimizationruntimechunk

相关文档:https://www.webpackjs.com/guides/code-splitting/#entry-dependencies

作用:webpack打包后,会在每个入口文件中插入 webpack 运行时代码,当有多个入口文件时,就显得重复了,因此可以使用这个配置将运行时代码抽离出来,可以减少代码体积。

运行时代码就是 Webpack 在打包过程中生成的一些辅助代码,负责模块的加载和执行。

使用:

// webpack.config.js
module.exports = {
    // ...
	optimization: {
		/* 
            - 值为 single 时,会将 所有入口文件共享的运行时代码提取到一个单独的文件中。
            - 值为 multiple 或 true 时,会将 每个入口文件都会有自己独立的运行时代码文件。
            - 当值为一个函数时,可以设置生成的 runtime 文件的名称。
        */
        runtimeChunk: 'single',
    }
}

使用后,会发现打包后,多出来一个运行时的文件。

补充:

  • 值为 true 和 multiple 时,适合多页面应用使用,每个页面都有自己的入口文件,都使用 自己的运行时代码;当然也可以多个页面都使用一个运行时文件。

  • 如果是 单页面应用,但是有 多个入口文件,使用 true 或 multiple 时,可能会出现bug,比如每个文件执行两次。 所以单页面应用,推荐使用 single。

  • 还有一种方式也可以抽离出来

    // webpack.config.js
    module.exports = {
    	entry: {
            index: {
                import: './src/index.js',
                // 如果 两个入口文件的 runtime 值一样,则会生成一个共享的 runtime 文件,类似 single
                // 如果不一样,则各自生成各自的 runtime 文件,类似 multiple 
                runtime: 'common-runtime', // 指定生成的 runtime 文件名的前缀
            },
            about: {
                import: './src/about.js',
                runtime: 'common-runtime',
            },
        }
    }

懒加载

  • 在 webpack 中,使用 import 动态导入,就会单独的创建一个 chunk,在需要用到的时候,才会加载 文档:https://www.webpackjs.com/guides/code-splitting/#dynamic-imports

  • 在 react 中的使用方式如下

    import {Suspense, lazy} from 'react';
    // 懒加载的组件
    const Home = lazy(() => import(/* webpackChunkName: "home" */'./Home'));
    const About = lazy(() => import(/* webpackChunkName: "about" */'./About'));
    
    export default function App() {
        return (
            <>
                <ul>
                    <li><Link to="/home">Home</Link></li>
                    <li><Link to="/about">About</Link></li>
                </ul>
    			// 被 Suspense 组件包裹才可
                <Suspense>
                    <Routes>
                        <Route path="/home" element={<Home />} />
                        <Route path="/about" element={<About />} />
                    </Routes>
                </Suspense>
    
            </>
        );
    }

stylelintWebpackPlugin优化

  1. 当项目非常大时,可以开启多进程 和 开启缓存
new StylelintWebpackPlugin({
    files: ['src/**/*.{css,less}'],
    threads: true, // 默认 false
    cache: true, // 默认 true
    cacheLocation: resolve(__dirname, './node_modules/.cache/stylelint-cache'), // 默认值
})

babel-loader优化

  1. 开启babel-loader的缓存,以及关闭缓存压缩
  2. 使用 @babel/plugin-transform-runtime 插件将 @babel/runtime 辅助函数改为从 node_modules 引入的形式
  3. @babel/preset-env 开启按需引入 corejs,将 useBuiltIns: 'usage'
export const jsLoader: RuleSetRule = {
    test: /\.[j|t]s$/,
    exclude: /node_modules/,
    use: [
        {
            loader: 'babel-loader',
            options: {
                cacheDirectory: true, // 开启缓存
                cacheCompression: false, // 关闭 压缩缓存
                presets: [
                    [
                        '@babel/preset-env', {
                            useBuiltIns: 'usage', // 按需引入 core-js
                            corejs: '3.32.0',
                        }
                    ]
                ],
                plugins: [
                    '@babel/plugin-transform-runtime', // 提取辅助函数
                ]
            }
        }
    ]
}

eslint优化

  1. 开启 eslint 缓存,默认就是开启的
  2. 项目比较大时,开启多线程,但是目前开启多线程还有会导致校验失效,暂不开启
// 校验js
new EslintWebpackPlugin({
    context: join(process.cwd(), '/src'),
    files: ['**/*.{js,ts,jsx,tsx}'], // 指定要校验的目录、文件或 globs ,必须是相对于 options.context 的相对路径
    cache: true, // 默认就是 true
    cacheLocation: join(process.cwd(), '/node_modules/.cache/eslint-webpack-plugin/.eslintcache'), // 默认就是这个值
    // TODO 开启多线程 导致eslint校验失效,
    // issues回答 还没有解决: https://github.com/webpack-contrib/eslint-webpack-plugin/issues/146
    threads: false, // 开启多线程 默认false 
})

生产模式下优化

压缩 css:css-minimizer-webpack-plugin

压缩js: terser-webpack-plugin 默认就有

压缩图片:image-minimizer-webpack-plugin

output

output文档: https://www.webpackjs.com/configuration/output/

有好多配置,也有跟多配置搞不懂,慢慢来吧 😭

{
	// output文档: https://www.webpackjs.com/configuration/output/
    output: {
        filename: 'js/[name]_[contenthash:5].js',
        // 此选项决定了非初始(non-initial)chunk 文件的名称
        chunkFilename: 'js/chunk_[name]_[contenthash:5].js',
        // 与 output.filename 相同,不过应用于 Asset Modules。
        assetModuleFilename: 'asset/[name].[id:5][ext][query]',
        // 创建按需加载的异步 chunk。默认 true,为 false 时,import 动态加载就不会单独生成一个 chunk
        asyncChunks: true,
        path: path.resolve(__dirname, './dist'),
        clean: true,
    }
}

output.library

{
    output: {
        // 文档:https://www.webpackjs.com/configuration/output/#outputlibrary
        // 打包后,以什么样的格式输出,比如 var amd umd commonjs2 等等
        library: {
            // 指定库的名称。其他模块可以直接使用这个名称来访问
            // name: 'test_aa',
            // 配置将库暴露的方式
            type: 'umd',
            // 给 umd 添加注释,感觉没啥用,生产环境下,注释都自动的删除了
            // auxiliaryComment: {
            //     root: 'Root Comment',
            //     commonjs: 'CommonJS Comment',
            //     commonjs2: 'CommonJS2 Comment',
            //     amd: 'AMD Comment',
            // }
        },
    }
}

输出amd格式1

  1. 需要现将 webpack.config.js 中的 library.type 改为 amd,library.name 也要设置

    module.exports = {
        output: {
            library: {
                // 指定库的名称。其他模块可以直接使用这个名称来访问
                name: 'MyModule',
                // 配置将库暴露的方式
                type: 'amd',
            },
        }
    }
  2. index.html 中添加 requirejs,不添加的话,打包后会报错,提示不存在 define

    <head>
        <meta charset="UTF-8">
        <title><%= title %></title>
        <script src="https://cdn.jsdelivr.net/npm/requirejs@2.3.5/require.min.js"></script>
    </head>
    <body></body>
    <script>
        // library.name 指定的名称,引用一下,才会执行打包后的代码
        require(['MyModule'], function (index) {
            console.log(index);
        })
    </script>
  3. 打包后试试

输出amd格式2

跟上面的那种方式比起来就简单很多,不需要在html中手动引入打包后的文件,也可以不用设置 library.name

  1. library.type 改为 amd-require

    module.exports = {
        output: {
            library: {
                // 配置将库暴露的方式
                type: 'amd-require',
            },
        }
    }
  2. index.html 中添加 requirejs,不添加的话,打包后会报错

    <head>
        <meta charset="UTF-8">
        <title><%= title %></title>
        <script src="https://cdn.jsdelivr.net/npm/requirejs@2.3.5/require.min.js"></script>
    </head>
  3. 打包后试试

resolve

用来设置模块该如何被解析

文档:https://www.webpackjs.com/configuration/resolve/

alias

取别名

module.exports = {
	resolve: {
        alias: {
            '@': path.resolve(__dirname, './src'),
        }
    },
}

extensions

省略后缀

module.exports = {
    resolve: {
        // '...' 表示 继承webpack默认的省略后缀配置
        // 默认值为 ['.js', '.json', '.wasm'],
        extensions: ['...', '.ts', '.tsx', '.jsx'],
    },
}

fallback

模块解析失败时,重定向模块请求。

webpack 5 不再自动 polyfill Node.js 的核心模块,这意味着如果你在浏览器或类似的环境中运行的代码中使用它们

比如在 index.js 中使用 querystring 模块,它会提示你,如何去配置和安装

module.exports = {
    resolve: {
        fallback: {
            querystring: require.resolve('querystring-es3') // npm i querystring-es3
        }
    },
}

感觉没啥用,如果加载一个模块失败,那直接去安装这个模块不就行了,为什么非要在 fallback 中配置一下呢,顶多改个别名。

webpack默认支持的插件

DefinePlugin

允许在 编译时 将你代码中的变量替换为其他值或表达式。

// webpack.config.js
module.exports = {
	plugins: [
        // ...
        new webpack.DefinePlugin({
            TITLE: JSON.stringify('练习webpack'),
            'typeof window': JSON.stringify('123'), // 使用 typeof window 就会变成 123
        })
    ],
}

打包后

// index.js
console.log(TITLE); // 练习webpack
console.log(typeof window); // 123

ProvidePlugin

自动加载模块,而不必到处加载 import require 模块。

new ProvidePlugin({
    _: 'lodash',
})

在 index.js 文件中,直接使用即可

const arr = [1, 2, 3, 4];
console.log(_.includes(arr, 3));

EnvironmentPlugin

使用 DefinePlugin on process.env 键的简写

监听

watch

文档:https://www.webpackjs.com/configuration/watch/

npm run build 后,可以监听文件是否发生改变,重新编译

{
	watch: true,
    watchOptions: {
        aggregateTimeout: 1000, // 延迟1秒编译
        ignored: /node_modules/, // 忽略的文件
        // poll: 1000, // 自动刷新,也可以设置数字 
    }
}

HMR

就是 热更新,当有文件发生改变时,自动重新的编译生成最新的结果。

webpack-dev-server 一般是默认开启的

还需要在入口文件中配置,写法有很多,详见文档。

文档:https://www.webpackjs.com/guides/hot-module-replacement/

if (module.hot) {
    module.hot.accept(function() {
        console.log('Accepting the updated printMe module!');
    });
}

但是一般都不这样写,各个框架有各个框架的插件。

react 更新插件

插件:https://www.npmjs.com/package/@pmmmwh/react-refresh-webpack-plugin

  1. 修改 webpack.config.js 的plugins 配置

    import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
    export default {
        plugins: [
            new ReactRefreshWebpackPlugin()
        ]
    }
  2. 修改 babel-loader 的 plugins 配置

    {
        loader: 'babel-loader',
        options: {
          	// ...
            plugins: [
                isDev ? 'react-refresh/babel' : ''
            ]
        }
    }

externals

排除依赖,打包时不打包进最后的源码中

文档:https://www.webpackjs.com/configuration/externals/

// webpack.config.js
module.exports = {
	// 控制哪些包最后不会被打包进最终的dist目录中
    // externalsType: 'umd', // 指定 externals 的类型
    externals: { // 文档:https://www.webpackjs.com/configuration/externals/
        lodash: '_'
    },
    // 加载cdn
    // externalsType: 'script',
    // externals: {
    //     'lodash': ['https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js', '_'],
    // }
}

preload 和 prefetch

文档:https://www.webpackjs.com/guides/code-splitting/#prefetchingpreloading-modules

  • prefetch(预获取):将来某些导航下可能需要的资源
  • preload(预加载):当前导航下可能需要资源

问题

使用merge工具库遇到的问题

  1. 合并对象时,发现对象中的属性没有按照预期的合并
const obj1 = {a: {xx: 'xxx'}};
const obj2 = {a: {yy: 'yyy'}};
merge(obj1, obj2) // a: {yy: 'yyy'}
// 发现 obj1 中的 xx 被 yy覆盖掉了

// 可以使用这个属性
const obj1 = {a: {xx: 'xxx'}};
const obj2 = {a: {yy: 'yyy'}};
merge.recursive(obj1, obj2) // a: {xx: 'xxx', yy: 'yyy'}
  1. 如果子属性中有 复杂类型的数据,则后面的对象中的属性会替换掉之前的
const obj1 = {a: {xx: 'xxx'}, arr: [{a: 11}, {b: 22}]};
const obj2 = {a: {yy: 'yyy'}, arr : [{c: 22}]};
merge.recursive(obj1, obj2)
// {
//     "a": {
//         "xx": "xxx",
//         "yy": "yyy"
//     },
//     "arr": [
//         {
//             "c": 22
//         }
//     ]
// }
  1. 如果是合并webpack配置的时候,最好不要使用clone参数,也就是merge的默认第一个参数 如果填写了 true,merge会将一些实例对象clone出错
const obj1 = {a: {xx: 'xxx'}};
const obj2 = {a: {yy: 'yyy'}};
const obj3 = merge.recursive(true, obj1, obj2)
obj3 === obj1 // false
obj3 === obj2 // false

const obj1 = {a: {xx: 'xxx'}};
const obj2 = {a: {yy: 'yyy'}};
const obj3 = merge(true, obj1, obj2)
obj3 === obj1 // true
obj3 === obj2 // false

2023.7.28

有多入口文件时,只有一个文件的热更新有效果

版本

"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"

配置

import {Configuration, HotModuleReplacementPlugin} from 'webpack';
import 'webpack-dev-server';
import merge from 'merge';
import {baseConfig} from './webpack.base.config';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import {extname, resolve} from 'path';
import {readFileSync} from 'fs';

const PORT = 8888;
const {ENV} = process.env;
console.log(ENV);

const config: Configuration = {
    mode: ENV === 'development' ? 'development' : 'production',
    entry: {
        index: resolve(__dirname, '../src/index.js'),
        app: resolve(__dirname, '../src/app.js')
    },
    output: {
        path: resolve(__dirname, '../dist'),
        filename: 'js/[name].js',
        clean: true,
    },
    module: {
        rules: [
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            title: '自定义html', // 如果设置了 template,将以template中的内容为准
            filename: 'index.html',
            template: resolve(__dirname, '../src/index.ejs'),
            cache: true, // 默认为true
            hash: true, // 将注入的 js css等文件名身上加上hash,有助于清除缓存
            showErrors: true, // 默认为true,错误信息将写入html页面
            chunks: ['index', 'app'], // 插入一些文件
            templateParameters: { // 这个参数对象中的变量,可以直接在 template 模板中使用
                title: 'custom html',
                text: '我是h1标题',
            },
            inject: 'head',
            favicon: resolve(__dirname, '../src/img/favicon.ico')
        }),
    ],
    // optimization: {
    //     runtimeChunk: 'multiple'
    // },
    devServer: {
        compress: true,
        host: 'localhost',
        port: PORT,
        hot: false,
    }
}
export default merge.recursive(baseConfig, config);

当entry有多个入口文件时,只有一个入口文件的热更新生效,其他的入口文件会报错

image-20230208181754446

目前解决的办法

第一种:webpack-dev-server 中的 hot: false 设置成false

第二种:设置optimization

optimization: {
    runtimeChunk: 'multiple'
},

目前没有更好的解决方案

issues: https://github.com/webpack/webpack-dev-server/issues/2792

字体文件过大

打包时,字体文件过大,该如何解决?

使用 字体子集?会导致一些字符格式不对,使用cdn?暂时没有那个能力。还没有一个比较好的方法。

ejs 模板语法失效

单独使用 html-webpack-plugin 时,打包后一切正常,但是处理图片时,是不会自动处理图片路径的,会原封不动的输出到打包后的html中,导致图片加载不出来,因此想到了使用 html-loader ,会自动的帮忙处理一些路径。但是导致 ejs 模版语法失效,因为在没有使用 html-loader 时,html-webpack-plugin 默认使用的是内部基于 loader 模版的 ejs 语法,如果使用了其他加载器,就不会使用默认的了,因此类似这样的语法将会失效<title><%= title %></title>,该如何解决呢。

推荐笔记:https://www.cnblogs.com/feng-gamer/p/14836084.html

但是我使用的是另一种方案,这样,在 html-loader 中,就可以处理ejs的语法了

文档:https://webpack.docschina.org/loaders/html-loader/#templating

ejs文档:https://ejs.bootcss.com/#docs

// html
import ejs from 'ejs';
export const htmlLoader: RuleSetRule = {
    test: /\.html$/,
    loader: 'html-loader',
    options: {
        preprocessor: (content, loaderContext) => {
            let result;
            try {
                // 使用 ejs 处理 html
                result = ejs.render(content, {title: 'webpack练习'}, {});
            } catch (error) {
                loaderContext.emitError(error);

                return content;
            }

            return result;
        },
        esModule: false
    }
}

tsx文件不用引入React

文档:https://zh-hans.legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#manual-babel-setup

笔记:https://juejin.cn/post/7112343439731064846

如果ts报错,没有引入React,也可能是 tsconfig.json 没有配置好

https://www.typescriptlang.org/tsconfig#jsx

{
	"compilerOptions": {
        "jsx": "react-jsx", // 试试这个
    }
}

还有 @babel/preset-react 在 babel 8 才会默认导入 react 因此可能也需要配置

https://www.babeljs.cn/docs/babel-preset-react#both-runtimes

{
    loader: 'babel-loader',
    options: {
        cacheDirectory: true, // 开启缓存
        cacheCompression: false, // 关闭 压缩缓存
        presets: [
            //...
            [
                '@babel/preset-react', {
                    runtime: 'automatic' // 自动引入 React 不需要在手动引入
                }
            ]
        ],
        plugins: [
            '@babel/plugin-transform-runtime',
        ]
    }
}

eslint的话,也可能会提示,可能需要将这两个规则关闭

https://zh-hans.legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#eslint

'react/jsx-uses-react': 'off',
'react/react-in-jsx-scope': 'off'

补充

@地址,点击跳转

jsconfig.json或者tsconfig.json中配置

{
    "compilerOptions": {
        ...
        "baseUrl": "./",
        "paths": {
            "@/*": ["./src/*"],
        },
    },
    "exclude": ["node_modules", "dist"],
    "include": ["src/**/*"],
}

打印带颜色的文字

console.log('\x1b[31m', '这段文字是红色的', '\x1b[0m');

其中,\x1b[31m 表示设置输出颜色为红色,\x1b[0m 表示重置颜色。

此外,还有其他的 ANSI 转义码,可以实现更多不同样式的文本效果

const color = {
    bright: '\x1B[1m', // 亮色
    grey: '\x1B[2m', // 灰色
    italic: '\x1B[3m', // 斜体
    underline: '\x1B[4m', // 下划线
    reverse: '\x1B[7m', // 反向
    hidden: '\x1B[8m', // 隐藏
    black: '\x1B[30m', // 黑色
    red: '\x1B[31m', // 红色
    green: '\x1B[32m', // 绿色
    yellow: '\x1B[33m', // 黄色
    blue: '\x1B[34m', // 蓝色
    magenta: '\x1B[35m', // 品红
    cyan: '\x1B[36m', // 青色
    white: '\x1B[37m', // 白色
    blackBG: '\x1B[40m', // 背景色为黑色
    redBG: '\x1B[41m', // 背景色为红色
    greenBG: '\x1B[42m', // 背景色为绿色
    yellowBG: '\x1B[43m', // 背景色为黄色
    blueBG: '\x1B[44m', // 背景色为蓝色
    magentaBG: '\x1B[45m', // 背景色为品红
    cyanBG: '\x1B[46m', // 背景色为青色
    whiteBG: '\x1B[47m' // 背景色为白色
}
成就
0
Star
0
Fork
成员(1)
qxy

搜索帮助

23e8dbc6 1850385 7e0993f3 1850385