原文

https://github.com/CodeLittlePrince/blog/issues/9

前言

有时候,市面上的webpack loader并不完全符合我们的需求,所以,我们不得不自己从0开始写一个,或者是在别人写的loader基础之上进行修改。
无论哪种,都需要我们对webpack加载loader的方式有所了解。

实现

出招吧~

在github上创建项目

clipboard.png

创建本地项目

1、git clone项目到本地
2、初始化npm

npm init

填写完npm init的一路提示下来以后,我们看下文件结构:

.
├── README.md
└── package.json

3、安装webpack

npm i -D webpack

4、设置一下package.json里的scripts命令:

"scripts": {
  "dev": "webpack"
},

这样的话,基本的工具就准备完毕了。

编写webpack.config.js

1、创建webpack.config.js

.
├── README.md
├── node_modules
├── package-lock.json
├── package.json
└── webpack.config.js

2、编辑webpack.config.js

const path = require('path')

module.exports = {
  entry: {
    app: path.resolve('demo/index.js')
  },
  output: {
    path: path.resolve('dist'),
    filename: 'index.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: path.resolve('src/loader-test.js'),
        options: {
          speak: 'wang~',
        }
      }
    ]
  }
}

因为我们是从0开始编写的,所以不得不先从简单到复杂。
所以,如上,我们通过path引用的方式来使用loader。并且,我们配置了option,作为参数。
index则是需要处理的文件。

编写index.js

const cat = 'kitty'
console.log(cat)

编写loader-test.js

// loader-utils作为工具类引入(作为webpack依赖,所以在安装webpack时候就带上了)
const loaderUtils = require('loader-utils')

// loader调用的时候,会将源数据和sourcemap作为参数传入函数
module.exports = function(source, inputSourceMap) {
  const code = source
  const map = inputSourceMap
  // loaderUtils.getOptions 可以获取到设置loader时候设置的options
  // 当然loaderUtils还有很多其他有用的方法,详情可以看 https://github.com/webpack/loader-utils
  const loaderOptions = loaderUtils.getOptions(this) || {};
  console.log(source)
  console.log(loaderOptions)
  // loader需要将自己的值传给下一个loader,并且,loader不免会有异步操作
  // 因此需要回调来证明自己已经处理结束了
  this.callback(null, code, map)
}

先看下目录结构,为了不影响视觉,我忽略了node_module文件:

.
├── README.md
├── demo
│   └── index.js
├── package-lock.json
├── package.json
├── src
│   └── loader-test.js
└── webpack.config.js

好,让我们运行一下webpack,看一下效果:
npm run dev

...
const cat = 'kitty'
console.log(cat)
{ speak: 'wang~' }
...

正如我们写的loader,打印出了index.js的源码,以及,webpack.config.js配置loader时候的options。
是不是有点儿小兴奋?
clipboard.png

写点有意义的功能

虽然说是教程,但是这样的小例子的确有点太过简单了,我们可以做点有意义点的功能。
比如,我们想把js中px全部替换成vw,比例就按照1vw = 10px吧。
(我相信很多朋友会觉得为啥替换js,而不是css或者scss。因为,会涉及更多的webapck配置,比较无聊和对本章内容没什么作用,所以,我觉得还是越简单越好,就拿js举例子吧)
好,计划有了,开始行动吧!

重新编辑index.js

const parentStyle = `
  background: #fdc;
  width: 1200px;
  height: 600px;
  box-sizing: border-box;
  padding: 150px 300px;
`
const childStyle = `
  background: #cdf;
  width: 600px;
  height: 300px;
`
const parent = document.getElementById('parent')
const child = document.getElementById('child')
parent.style.cssText = parentStyle
child.style.cssText = childStyle

为了更好展示,我们再写个html吧

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    * {padding: 0; margin: 0;}
  </style>
</head>
<body>
  <div id="parent">
    <div id="child"></div>
  </div>
</body>
</html>

让启动demo更顺畅

一不做二不休,为了更顺畅的看效果,我们加个webpack-dev-server自动启动吧。同时,顺带着,将html-webpack-plugin和clean-webpack-plugin也都加上。
关于写demo,我觉得,是写npm modules必须要有的东西,如果没有demo,没有顺畅的启动demo操作。别说别人懒得看,自己都懒得启动了。
好,我们再看下现在的webpack配置:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
  entry: {
    app: path.resolve('demo/index.js')
  },
  output: {
    path: path.resolve('dist'),
    filename: 'index.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: path.resolve('src/loader-test.js')
      }
    ]
  },
  plugins: [
    // 清理dist
    new CleanWebpackPlugin('dist'),
    // 将js打入html
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: path.resolve('demo/index.html'),
      chunks: ['app'] // 因为只有一个页面,这行不写也可以
    })
  ]
}

修改下package.json里的scripts命令:

"scripts": {
  "dev": "webpack-dev-server --open"
},

然后,启动实验一下,npm run dev
看下效果:
clipboard.png

没问题,进入下一步~

正式修改loader

让我们重新编辑loader-test.js吧:

  ...
  // 替换px
  const regex = /(\d+?)px/g
  code = code.replace(regex, function(match, p1) {
    return p1/10 + 'vw'
  })
  ...
}

然后,再重新启动一下,我们会发现,px都被替换成了vw了,而且比例为1vw = 10px,成功!
当然,有同学肯定会想到,要是这个比例可以自己设置那就更好了。实现方式当然也很简单啊,还记得我们之前是怎么获取loader中options配置的speak吗?我相信同学完全可以独立完成了。

怎么把包做成npm module,然后发到npm 上,以后都能用呢?

这个的话,其实是我之前已经写过这样的文章了,同学们可以转到npm-从0开始写一个npm module

本文项目地址

没错~点我>>

最后,希望喜欢的同学能给star哦

说点题外话,不知道为什么webpack官网对loader的介绍那么简短,很难单单根据文档就写出loader来。所以还建议看些别人写的loader,如babel-loader等。


岁月是把杀猪刀
1.6k 声望1.4k 粉丝

もっと遠くにあるはずの、とこか、僕はそこに行きたいんだ