Webpack4散记(5)写一个loader

445 阅读3分钟

看一下webpack官方对loader的描述文档: webpack.js.org/contribute/…

A loader is a node module that exports a function. This function is called when a resource should be transformed by this loader. The given function will have access to the Loader API using the this context provided to it.

就感觉很高级

上面这三句话,有三个知识点:

  1. loader是一个function,使用commonjs导出。(模块管理、导出类型)
  2. loader的作用是转换资源。(函数需要返回值)
  3. loader这个函数需要使用this上下文,来使用LoaderAPI提供的接口。(不能是箭头函数)

那么可以确定,代码层级的loader应该是下面的样子

module.exports = function (source) {
    // ...do something
    return output
}

其实上一篇《Webpack4散记(4)JS和CSS的打包优化》中提到的,最终使用MiniCSS和OptimizeCss来实现CSS代码压缩的方案,自我感觉并不理想,原因如下:

  1. 代码剥离和代码压缩两个行为合并,偏离最初的纯压缩目标。
  2. 代码包数量double,导致页面加载阶段并发请求过多。
  3. 使用@loadable/components时,按需加载的包也double了,不好管理,而且丑。

今天又找了一波资料,搜了npm,并未发现自己所需的工具。所以决定自己动手搞一个。

编写css-min-loader代码

对代码的处理,一定是CODE -> AST -> CODE这个路径,不要考虑正则或者字符串替换之类。

对CSS解析OOM的工具一大把,找个元老级的css包:www.npmjs.com/package/css

文档中提到:

css.stringify(object, [options])

关于第二个参数options

options:

  • indent: the string used to indent the output. Defaults to two spaces.
  • compress: omit comments and extraneous whitespace.
  • ...

那么事情就简单了,直接撸代码:

const css = require('css')

module.exports = function (source) {
    const ast = css.parse(source)
    const min = css.stringify(ast, { compress: true, indent: '' })
    return min
}

将自定义loader嵌入webpack

在webpack中,loader的调用是通过require方式;每个loader配置,一定少不了用于require的调用路径。

{
    test: /\.css$/,
    use: [
        'style-loader',
        'css-loader',
        {
            loader: path.resolve(__dirname, './css-min-loader.js')
        }
    ],
    exclude: /node_modules/,
},
{
    test: /\.less$/i,
    use: [
        'style-loader',
        'css-loader',
        {
            loader: path.resolve(__dirname, './css-min-loader.js')
        },
        {
            loader: 'less-loader',
            options: {
                lessOptions: {
                    javascriptEnabled: true,
                    // strictMath: true,
                }
            },
        }
    ],
},

再啰嗦一句:一组loader的调用顺序,是从下向上的。比如上面的css组,是

css-min-loader -> css-loader -> style-loader

的顺序;而less组是:

less-loader -> css-min-loader -> css-loader -> style-loader

换句话说,css-min-loader需要传入原始的css代码,将压缩好的代码传给后续的loader处理。因为后续loader也都遵循基于AST处理的方式,所以对后续而言,css-min-loader的压缩是没有副作用的。

未使用 css-min-loader

         Asset       Size  Chunks                         Chunk Names
  antd.8c2d.js   99.3 KiB       0  [emitted] [immutable]  antd
client.ca88.js   23.8 KiB       1  [emitted] [immutable]  client
common.fb46.js    136 KiB       2  [emitted] [immutable]  common
    index.html  208 bytes          [emitted]     
    
Entrypoints:
  client (259 KiB)
      common.fb46.js
      antd.8c2d.js
      client.ca88.js

使用 css-min-loader

         Asset       Size  Chunks                         Chunk Names
  antd.564f.js   86.9 KiB       0  [emitted] [immutable]  antd
client.faf7.js   23.7 KiB       1  [emitted] [immutable]  client
common.fb46.js    136 KiB       2  [emitted] [immutable]  common
    index.html  208 bytes          [emitted]              
    
Entrypoints:
  client (247 KiB)
      common.fb46.js
      antd.564f.js
      client.faf7.js

可以看到,体积降低了大约12KiB。

关于loader的编写,就到这里。

后续

再次检查输出产物,发现css对代码的压缩效果并不是十分理想,对带有prefix的样式、rgba或cubic-bezier后的值,并没有做到最佳优化。也有朋友推荐cssnano,有待进一步尝试。

另外,css-min-loader已经发布到npm,也就是说可以直接使用:

{
    test: /\.css$/,
    use: [
        'style-loader',
        'css-loader',
        'css-min-loader'    // 这里
    ],
},

来配置webpack了。上面提到的压缩优化的问题,也会在这个项目里继续改进。

NPM: www.npmjs.com/package/css…

GIT: github.com/imnull/css-…

以上。