通过css-loader讲透webpack loader机制(下)

636 阅读1分钟

上一篇文章介绍了 css-loader 对 css 文件的编译,但是编译后的代码并没有应用到 DOM 中,所以还需要style-loadercss-loader编译后的代码写入到 DOM 中。

style-loader做了什么?

首先安装style-loader,配置如下:

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devtool: 'source-map',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
]

注意: loader的执行顺序是从下到上,从右到左

style-loader具体代码实现如下:

  1. 创建style标签
function insertStyleElement(options) {
  var element = document.createElement('style')
  return element
}
  1. 生成css代码字符串,并插入到<style>标签中
function apply(styleElement, options, obj) {
  // css字符串
  var css = ''

  // 如果css文件中存在媒体查询media
  if (obj.media) {
    css += '@media '.concat(obj.media, ' {')
  }

  css += obj.css

  if (obj.media) {
    css += '}'
  }
  // 插入到\<style>标签中
  options.styleTagTransform(css, styleElement, options.options)
}

function styleTagTransform(css, styleElement) {
  styleElement.appendChild(document.createTextNode(css))
}

通过 style-loader 就真正实现了css in js的概念,这样样式就生效了。

如果把 style-loadercss-loader 执行顺序颠倒下,打包的时候会报错。因为需要先用css-loader 对css文件进行模块化,即变成一个module,然后 style-loader 基于module转化为DOM,所以他们的执行顺序是不能颠倒的。

如何编写一个loader

解析.ve的loader

我们可以创建一个以've'结尾的文件index.ve,然后在index.js引入:

// index.ve
<script>
export default {
  a: 1,
  b: 2
}
</script>

// index.js
import './index.css'
import ve from './index.ve'

console.log('hello webpack')
console.log('ve', ve)

执行npm run build,会报错,需要一个loader来解析以've'结尾的文件。现在,我们来手写一个loader,创建一个loader/veloader.js文件:

const reg = /<script>([\s\S]+?)<\/script>/

module.exports = function (source) {
  console.log('loader running', source)
  const __source = source.match(reg)
  return __source && __source[1] ? __source[1] : source
}

source就是index.ve里面的内容,然后通过正则把<script>标签去掉,只保留js字符串。打印结果:

{
   a: 1,
   b: 2
}

这样我们定义的以've'结尾的文件就能正常被编译和执行了。

注意:非js文件使用loader处理后一定要是js引擎能识别的,什么意思呢?上面的代码去掉<script>,就是一个js引擎能识别的,如果把export default也去掉,就是{a: 1, b: 2},这个在语法上就是一个错误的,更无法被js引擎所识别。如果一个文件没有export default,那么我们返回的时候需要给它加上export default。比如: .vue文件它里面就有一个export default导出。

替换特定字符的loader

首先创建loader/replace-loader.js,这段代码的意思是用我们传入的字符替换hello字符串。

replace-loader.js

module.exports = function (source) {
  const options = this.getOptions()
  const code = source.replace('hello', options.name)
  return code
}

replace.js

export function getStr(str) {
  return 'hello' + str
}

index.js

import { getStr } from './replace'
console.log(getStr('pengchangjun'))

webpack.config.js

module: {
  rules: [
    {
      test: /\.js$/,
      use: [
        {
          loader: path.resolve(__dirname, './loader/replace-loader.js'),
          options: {
            name: '你好:'
          }
        }
      ]
    }
  ]
},

编译打包后,打印结果: '你好:pengchangjun',说明'hello'字符被我们传入的'你好:'所替换了。

编写loader的注意事项

  • 因为在loader执行过程中,要把this指向为webpack,所以不能使用箭头函数。
  • 入口文件引入模块文件是像这样import ve from './index.ve'引入的,所以index.ve最后编译后的代码一定要有export 或者 export default这样的导出语句,如果没有,可以 return的时候,加上module.exports = ${JSON.stringify(html)}

loader的应用场景

场景一:你想对一个老项目做前端监控,即错误代码进行捕获,那么我们需要对函数进行包裹

try {
  function() {}
} catch(e) {
  ...
}

如果我们手动修改,将非常困难,那么这个时候就可以使用loader,遇到function(){}可以用try/catch进行包裹。

场景二:如果你的网站是中英文网站,那么可以先用占位符把位置站住,然后通过一个全局变量告诉loader,然后进行中英文的替换:

if (node全局变量 === ‘中文’) {
  source.replace('{{title}}', '中文标题')
} else {
  source.replace('{{title}}', 'english title')
}