webpack 学习笔记系列04-资源处理优化

2021-02-21 21:08:05 浏览数 (1)

webpack 学习笔记系列04-资源处理优化

Write By CS逍遥剑仙 我的主页: csxiaoyao.com GitHub: github.com/csxiaoyaojianxian Email: sunjianfeng@csxiaoyao.com QQ: 1724338257

1. 配置 typescript

1.1 tsconfig.json

新建 ts 文件:

代码语言:txt复制
// hello.ts
function sayHello(name: string) {
    return 'Hello, '   name;
}
let name = 'csxiaoyao';
console.log(sayHello(name));

直接在命令行安装 ts 并编译:

代码语言:txt复制
$ npm i -g typescript
# 编译为 hello.js
$ tsc hello.ts

编译后的文件:

代码语言:txt复制
function sayHello(name) {
    return 'Hello, '   name;
}
var name = 'csxiaoyao';
console.log(sayHello(name));

TypeScript 约定了 tsconfig.json 文件来存储项目配置,文档链接。

代码语言:txt复制
{
    "compilerOptions": {
        "outFile": "dist/main.js",
        "sourceMap": true
    },
    "files": ["src/index.ts", "src/source.ts"]
}

若使用 tsconfig.json 文件,只需要执行 tsc 即可。

代码语言:txt复制
$ tsc

1.2 webpack 集成 typescript

安装 ts-loader:

代码语言:txt复制
$ npm i ts-loader --save-dev

配置 webpack.config.js

代码语言:txt复制
module.exports = {
    entry: './src/app.ts',
    output: {
        filename: 'app.js',
        path: './dist'
    },
    resolve: {
        extensions: ['', '.webpack.js', '.web.js', '.ts', '.js']
    },
    module: {
        loaders: [
            {
                test: /.ts$/,
                loader: 'ts-loader'
            }
        ]
    }
};

配合项目根目录的 tsconfig.json文件即可实现 ts 的集成。

2. css 处理

2.1 css-loader

webpack 中一切皆模块,css 文件可以在 JavaScript 中被直接引用,但不能解析 css 语法,css-loader 能将 css 转成字符串供 js 使用。

代码语言:txt复制
$ npm i -D css-loader

添加 rule:

代码语言:txt复制
{
    module: {
        rules: [
            {
                test: /.css$/,
                use: ['css-loader']
            }
        ]
    }
}

或直接使用内联写法:

代码语言:txt复制
import css from 'css-loader!./css/index.css';
console.log(css);

2.2 style-loader

style-loader 是将 css-loader 打包好的 CSS 代码以 <style> 标签的形式插入到 HTML 文件中,所以 style-loader 是在 css-loader 之后成对出现。

代码语言:txt复制
$ npm i -D style-loader

添加 rule:

代码语言:txt复制
module: {
    rules: [
        {
            test: /.css$$/,
            use: ['style-loader', 'css-loader']
        }
    ]
}

或直接使用内联写法:

代码语言:txt复制
import css from 'style-loader!css-loader!./css/index.css';
console.log(css);

2.3 mini-css-extract-plugin

在将 css 以 <style> 标签的形式插入到 HTML 文件中的基础上,还需要将 css 以 <link> 标签的形式通过 URL 引入,此时需要借助 mini-css-extract-plugin 这个插件。

代码语言:txt复制
$ npm install --save-dev mini-css-extract-plugin

配置文件需要同时修改 loader 和 plugin,loader 需要放在 css-loader 之后代替 style-loader:

代码语言:txt复制
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    plugins: [
        // 添加 plugin
        new MiniCssExtractPlugin({
            filename: '[name].css',
            chunkFilename: '[id].css'
        })
    ],
    module: {
        rules: [
            {
                test: /.css$/,
                // 添加 loader
                use: [MiniCssExtractPlugin.loader, 'css-loader']
            }
        ]
    }
};

2.4 CSS Modules

编译工具可以实现 css 的模块化,参考 css-loader。

来,这时候就需要使用mini-css-extract-plugin这个插件了,首先安装它:

代码语言:txt复制
{
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            modules: true
                        }
                    }
                ]
            }
        ]
    }
}

打包后的 class 会被替换以实现模块化。

2.5 CSS 预处理器

常见的预处理器有:less、sass(scss)、stylus,此处以 less 为例。

代码语言:txt复制
$ npm i -D less-loader

修改 webpack 配置:

代码语言:txt复制
// webpack.config.js
module.exports = {
    module: {
        rules: [{
            test: /.less$/,
            use: [
                'style-loader',
                {
                    loader: 'css-loader',
                    options: {
                        modules: true
                    }
                },
                'less-loader' // 将 less 编译为 css
            ]
        }]
    }
};

2.6 CSS 后处理器 PostCSS

2.6.1 PostCSS

PostCSS 是一个使用 JavaScript 插件来转换 css 的工具, 不仅可以处理 CSS 和预处理器语法,还能处理添加前缀、最新语法转义、压缩、甚至扩展 CSS 的语言特性等。其实现与 babel 类似,将 CSS 解析成 AST 再转换生成新 CSS。PostCSS 可以让 css 的编写更加轻松,如根据适配的浏览器使用 Autoprefixer 插件自动添加前缀适配不同浏览器。

代码语言:txt复制
/* index.css */
.flex {
    display: flex;
}

/* 经 postcss autoprefixer 处理后 */
.flex {
    display: -webkit-box;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex;
}
2.6.2 postcss-loader

安装 postcss-loader:

代码语言:txt复制
$ npm i -D postcss-loader

修改 webpack.config.js,postcss-loader 需要在 css-loader 或预处理器 loader 之前。

代码语言:txt复制
module.exports = {
    module: {
        rules: [
            {
                test: /.less$/,
                use: [
                    'style-loader', 
                    {
                        loader: 'css-loader',
                        options: {
                            modules: true,
                            importLoader: 2
                        }
                    },
                    'less-loader',
                    'postcss-loader'
                ]
            }
        ]
    }
};
2.6.3 postcss 配置

PostCSS 本身只是将 CSS 解析成 AST,需要依赖其强大的插件系统才能实现丰富的功能,配置写法有三种:

  1. 项目的根目录下的配置文件 postcss.config.js
  2. webpack 配置文件中对应 loader 的配置项 options
  3. 直接在 package.json 中添加 postcss 属性

方式1: postcss.config.js

代码语言:txt复制
// postcss.config.js
const autoprefixer = require('autoprefixer');
module.exports = {
    plugins: [autoprefixer(['IE 10'])]
};

方式2: loader 配置项 options

代码语言:txt复制
// 引入postcss 插件
const autoprefixer = require('autoprefixer');
module.exports = {
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            // plugins 选项
                            plugins: [autoprefixer(['IE 10'])]
                        }
                    }
                ]
            }
        ]
    }
};

方式3: package.json 中添加 postcss 属性

受限于 json 语法,可扩展性较弱,不推荐。

代码语言:txt复制
{
    "postcss": {
        "plugins": {
            "autoprefixer": "IE 10"
        }
    }
}
2.6.4 常用 postcss 插件

autoprefixer

主要参数是 browserslist,用于给 css 补齐各种浏览器私有前缀,如 -webkit、-moz 、-ms 等,同时还会处理各种兼容性问题,如 flex 处理成 -webkit-box 等。

postcss-preset-env

与 babel 的 preset-env 类似,可以安心地使用最新 css 语法来写样式,如 cssnext 等。

PreCSS

可以写类 sass 和 cssnext 语法的 CSS。

cssnano

根据 CSS 语法解析结果智能压缩代码,比如合并一些类写法、缩短颜色值等一些常见的值。

postcss-import

支持处理 @import 引入的 CSS 代码,效果和设置 css-loader 的 importLoaders 相同。

importLoaders 表示 css-loader 作用于 @import 的资源之前有多少个 loader 取值: 0 => 默认,没有 loader 1 => postcss-loader 2 => postcss-loader, sass-loader

代码语言:txt复制
// 直接使用 postcss-import 插件
const autoprefixer = require('autoprefixer');
const postcssImport = require('postcss-import');
module.exports = {
    plugins: [postcssImport(), autoprefixer(['IE 10'])]
};

2.7 css 压缩处理

cssnano 是基于 postcss 的插件包,集成了30多种插件,能够实现多方面的优化,如:

  • 删除空格和最后一个分号
  • 删除注释
  • 优化字体权重
  • 丢弃重复的样式规则
  • 压缩选择器
  • 减少手写属性
  • 合并规则
代码语言:txt复制
// webpack.config.js
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
  plugins: [
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /.optimize.css$/g,
      cssProcessor: require('cssnano'), // 指定引擎,默认 cssnano
      cssProcessorPluginOptions: {
        preset: ['default', {discardComments: {removeAll: true}}]
      },
      canPrint: true
    })
  ]
};

在 CSS 中推荐使用 contenthash 占位符做文件的 hash 算法。

3. lint 工具处理代码风格质量

3.1 ESLint

ESLint 通过配置规则 (Rules) 来检测 JavaScript 语法规范,业内比较著名的配置规则有:Airbnb、Standard、Google 等,在项目中使用:

代码语言:txt复制
# 安装 CLI 工具
$ npm install -D eslint
# 创建配置文件 .eslintrc.json
$ eslint --init
# 运行检查
$ npx eslint

生成的 .eslintrc.json 配置文件如下:

代码语言:txt复制
{
    "env": {
        "browser": true,
        "es6": true
    },
    "extends": "airbnb-base",
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly"
    },
    "parserOptions": {
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "rules": {}
}

若想直接使用业界通用的规则,需要先安装规则:

代码语言:txt复制
# airbnb
$ npm install --save-dev eslint-config-airbnb
# google
$ npm install --save-dev eslint-config-google
# standard
$ npm install --save-dev eslint-config-standard

然后在 .eslintrc.json 配置文件中修改 extends 为对应值:

代码语言:txt复制
{
    "extends": "standard",
    "rules": {}
}

若要额外定制一些规则,可以配置 rules,ESLint 报错类型分三种:off (0)、warn (1)、error (2)。

代码语言:txt复制
{
    'rules': {
        // 禁止 console
        'no-console': 2,
        // 禁止 debugger
        'no-debugger': 2,
        // 禁止 alert
        'no-alert': 2,
        // 不用的 var,要删除,手动 tree shaking
        'no-unused-vars': 2,
        // 未定义的不能用,要用的写 eslint global 
        'no-undef': 2
    }
}

3.2 webpack 使用 eslint

安装 eslint-loader:

代码语言:txt复制
npm install -D eslint-loader

配置 rule:

代码语言:txt复制
{
    test: /.js$/,
    loader: 'eslint-loader',
    enforce: 'pre', // 手动调整 loader 加载顺序,保证先检测代码风格,再做 babel 转换等工作
    include: [path.resolve(__dirname, 'src')], // 指定检查的目录 
    options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
        formatter: require('eslint-formatter-friendly') // 指定错误报告的格式规范,需要额外安装
    }
}

注意:

  1. 不管作为独立的 module.rule 配置,还是放到 babel 的 rule 中,都要保证先通过 eslint-loader 检测代码风格
  2. TypeScript 需要用 tslint-loader 进行检测

JavaScript 代码规范,国

3.3 StyleLint

用于检测 css 语法,官方推荐的规则有:stylelint-config-recommendedstylelint-config-standard

代码语言:txt复制
$ npm install -D stylelint

此外,通过安装 stylelint-order 插件可以规范 css 书写顺序,如先写定位,再写盒模型,再写内容区样式,最后写 CSS3 相关属性。

StyleLint 的配置文件是 .stylelintrc.json,写法和 ESLint 配置类似:

代码语言:txt复制
{
    "extends": ["stylelint-config-standard", "stylelint-config-recess-order"],
    "rules": {
        // 支持 SCSS 语法中的 mixin、extend、content 语法
        "at-rule-no-unknown": [true, {"ignoreAtRules": ["mixin", "extend", "content"]}]
    }
}

3.4 webpack 使用 stylelint

通过 stylelint-webpack-plugin 插件来使用。

代码语言:txt复制
$ npm install -D stylelint-webpack-plugin

修改配置,可以配置 emitErrors 和 failOnError 参数。

代码语言:txt复制
const StyleLintPlugin = require('stylelint-webpack-plugin');
module.exports = {
    plugins: [new StyleLintPlugin(options)]
};

插件默认会查找项目中的 .stylelintrc.json 配置文件。

4. 静态资源处理

4.1 使用 file-loader / url-loader 加载图片资源

file-loaderurl-loader 是处理图片资源最常用的两个 loader,并且在一定应用场景下可以相互替代。

file-loader:根据配置项复制使用到的资源(不局限于图片)到构建后的文件夹,并更改对应的链接

url-loader:包含 file-loader 全部功能,并能根据配置转换为 Base64 方式引入

url-loader 为例,首先安装:

代码语言:txt复制
$ npm install -D url-loader

修改 webpack.config.js

代码语言:txt复制
module.exports = {
    module: {
        rules: [{
            test: /.(png|svg|jpg|gif)$/,
            use: {
                loader: 'url-loader',
                options: {
                    limit: 3 * 1024 // 不超过3k的图片才会被转换为base64
                }
            }
        }]
    }
}

4.2 配置CDN

修改 publicPath 配置:

代码语言:txt复制
module.exports = {
    output: {
        publicPath: 'http://xxx.com/img/'
    }
}

webpack 打包后结果如下:

代码语言:txt复制
<body>
    <img src="http://xxx.com/img/xxxxxx.png" />
    <script src="http://xxx.com/img/main.js"></script>
</body>

4.3 HTMLCSS 中使用 alias

修改 webpack 的 alias 配置

代码语言:txt复制
module.exports = {
    resolve: {
        alias: {
            '@assets': path.resolve(__dirname, './src/assets')
        }
    }
}

在html中使用alias:

代码语言:txt复制
<img src="~@assets/img/large.png" />

在css中使用alias:

代码语言:txt复制
.bg-img {
    background: url(~@assets/img/small.png) no-repeat;
}

Tips: 在 HTML 和 CSS 使用 alias 必须要前面添加 ~ 对于 svg 图片可以使用 svg-url-loader 处理

4.4 使用 img-webpack-loader 压缩优化图片

安装 loader:

代码语言:txt复制
$ npm install image-webpack-loader --save-dev

image-webpack-loader 不支持嵌入图片,所以必须和 url-loader 和 svg-url-loader 一起使用:

代码语言:txt复制
module.exports = {
    module: {
        rules: [{
            test: /.(jpe?g|png|gif|svg)$/,
            loader: 'image-webpack-loader',
            enforce: 'pre' // 提高优先级,在 url-loader 和 svg-url-loader 之前完成图片优化,避免在多个loader中重复引用
        }]
    }
};

4.5 使用 PostCSS 生成 CSS Sprite 雪碧图

安装 loader:

代码语言:txt复制
$ npm install postcss-sprites -D
$ npm install postcss-loader -D

修改 PostCSS 的 postcss.config.js,增加插件的调用:

代码语言:txt复制
// postcss.config.js 
const postcssSprites = require('postcss-sprites');
module.exports = {
    plugins: [
        postcssSprites({
            spritePath: './src/assets/img/' // 只有指定路径下的图片会被处理为精灵图
        })
    ]
};

配置 loader,注意顺序:

代码语言:txt复制
{
    test: /.css$/,
    use: [
    		MiniCssExtractPlugin.loader,
    		'css-loader', {
        		loader: 'postcss-loader'
        }
    ]
}

完成后,下面的图片引入会被处理为雪碧图:

代码语言:txt复制
/* 打包前 */
.bg-img01 {
    background: url(./assets/img/small-01.png) no-repeat;
}
.bg-img02 {
    background: url(./assets/img/small-02.png) no-repeat;
}

/* 打包后 */
.bg-img01 {
    background-image: url(xxxxxx.png);
    background-position: 0px 0px;
    background-size: 320px 320px;
}
.bg-img02 {
    background-image: url(xxxxxx.png);
    background-position: -160px 0px;
    background-size: 320px 320px;
}

4.6 使用 file-loader / url-loader 处理字体、富媒体资源

若不需要 Base64 可以直接使用 file-loader,否则用 url-loader:

代码语言:txt复制
{
    // 文件解析
    test: /.(eot|woff|ttf|woff2|appcache|mp4|pdf)(?|$)/,
    loader: 'file-loader',
    query: {
        // 这么多文件,ext不同,所以需要使用[ext]
        name: 'assets/[name].[hash:7].[ext]'
    }
}

4.7 使用 loader 导入数据资源

JSON / CSV / TSV / XML 格式资源可以通过对应 loader 导入为已解析的 JSON 以便于使用。 其中,JSON的 loader 是内置的,直接使用即可。

代码语言:txt复制
import Data from'./data.json'

对于其他三种数据格式,首先要安装对应的 loader:

代码语言:txt复制
$ npm i -D xml-loader csv-loader

然后修改 webpack 配置:

代码语言:txt复制
{
    test: /.(csv|tsv)$/,
    use: ['csv-loader']
}, {
    test: /.xml$/,
    use: ['xml-loader']
}

5. HTML 和多页面配置

5.1 HTML 文件处理

安装 loader:

将正则(test)放在一起,那么需要使用 ext 配置输出的文件名。

代码语言:txt复制
$ npm i html-webpack-plugin --save-dev

配置插件:

代码语言:txt复制
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    plugins: [
      	// case1: 在 dist 文件夹中自动生成一个 index.html 的文件,自动插入入口文件 main.js
      	new HtmlWebPackPlugin(),
        // case2: 修改自动生成的 html 文件参数
        new HtmlWebPackPlugin({ title: 'hello', filename: 'foo.html' }),
        // case3: 使用 template 模板文件生成 html 文件
      	new HtmlWebPackPlugin({ template: './src/index.html' })
    ]
};

5.2 多入口页面配置

对于多入口的 html 页面可以通过插件的多次实例化来实现,但仅配置插件,引入的入口 js 文件还是同样的 main.js,此时需要借助 html-webpack-plugin 插件的两个参数 chunks 和 excludeChunks 来解决。chunks 表示当前页面包含的 chunk,可以对应 entry 的 key,excludeChunks 则是排除某些 chunks。例如:

代码语言:txt复制
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
    mode: 'development',
    entry: {
        index: './src/index.js',
        list: './src/list.js'
    },
    plugins: [new HtmlWebPackPlugin({
        template: './src/index.html',
        filename: 'index.html',
        chunks: ['index']
    }), new HtmlWebPackPlugin({
        template: './src/list.html',
        filename: 'list.html',
        chunks: ['list']
    })]
};

Tips: 对于多页面应用,可以考虑使用 glob 等模块自动载入模板和入口文件来实现自动化加载

6. js 压缩处理

在 webpack 4 的 production 模式下已做了大量通用的优化配置,如 Tree-Shaking、Scope Hoisting 都默认开启,使用的压缩工具是 terser-webpack-plugin,是从 uglify-es 项目拉的一个分支以继续维护,具有和 uglifyjs-webpack-plugin 相同的参数。

代码语言:txt复制
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin(
        parallel: true, // 多线程
        comments: false,
        compress: {
          unused: true,
          drop_debugger: true, // 删掉 debugger
          drop_console: true, // 移除 console
          dead_code: true // 移除无用的代码
        }
      )]
  }
};

Scope Hoisting 作用域提升是指 webpack 通过 ES6 语法静态分析出模块之间的依赖关系,尽可能地把模块放到同一个函数中,让webpack 打包出来的代码文件更小、运行更快。 其他代码级别优化技巧:

  • 合理划分代码职责,适当按需加载
  • 合理设置 SplitChunks 分组
  • 合理使用 hash 占位符,防止文件名变化使 HTTP 缓存过期
  • 合理使用 polyfill,减少产生多余的代码
  • 使用 ES6 语法,减少使用有副作用的代码以加强 Tree-Shaking 效果
  • 使用 Webpack 的 Scope Hoisting(作用域提升)功能
  • 使用 bable-plugin-import 等工具优化一些 UI 组件库
  • 善用 webpack-bundle-analyzer 插件,帮助分析 Webpack 打包后的模块依赖关系

7. 缓存优化策略

静态资源可以设置 http 缓存策略,如:

代码语言:txt复制
Cache-Control: max-age=31536000

7.1 chunk 代码拆分

当使用 chunkhash 时,得益于缓存策略,代码拆分和按需加载就很重要,webpack 的 chunk 代码拆分方式大致有三种:

  • entry 配置:配置多个 entry 入口文件
  • 动态(按需)加载:代码中主动使用 import() 或 require.ensure
  • 抽取公共代码:使用 splitChunks 配置
代码语言:txt复制
module.exports = {
	// ...
  optimization: {
    splitChunks: {
      chunks: 'async', // "initial" | "all" | "async" (默认) 
      minSize: 30000, // 文件的最小尺寸,30K,development 下是10k,若此值很大,则公共部分不会被抽取,但按需加载仍会抽取
      maxSize: 0, // 文件的最大尺寸,0为不限制,优先级:maxInitialRequest/maxAsyncRequests < maxSize < minSize 
      minChunks: 1, // 默认1,被提取的一个模块至少需要在几个 chunk 中被引用,这个值越大,抽取出来的文件就越小 
      maxAsyncRequests: 5, // 在做一次按需加载的时候最多有多少个异步请求,为 1 的时候就不会抽取公共 chunk 了 
      maxInitialRequests: 3, // 针对一个 entry 做初始化模块分隔的时候的最大文件数,优先级高于 cacheGroup,所以为 1 的时候 就不会抽取 initial common 了 
      automaticNameDelimiter: '~', // 打包文件名分隔符 
      name: true, // 拆分出来文件的名字,默认为 true,表示自动生成文件名,如果设置为固定的字符串那么所有的 chunk 都会被合 并成一个 
      cacheGroups: {
        vendors: {
          test: /[\/]node_modules[\/]/, // 正则规则,如果符合就提取 chunk 
          priority: -10 // 缓存组优先级,当一个模块可能属于多个 chunkGroup,这里是优先级 
        },
        default: {
          minChunks: 2,
          priority: -20, // 优先级
          reuseExistingChunk: true // 如果该chunk包含的modules都已经另一个被分割的chunk中存在,那么直接引用已存在的c hunk,不会再重新产生一个
        }
      }
    }
  }
};
signsign

0 人点赞