Webpack入门到精通 三(Loader原理)

515 阅读3分钟

如何加载CSS文件

当前我们的打包器可以把多个文件打包到一个js文件中去,现在我们要支持当import xxx.css文件要可以生效。先来看一下我们目前的思路。

当前

  • 当前的bundler只能打包js文件
  • 我们想要加载css文件

目标

如果我们把css文件变成js,那么是不是可以加载css文件了呢?我们顺着这个思路开始在之前的代码中继续编写。我们只需要在读取到css文件之后修改文件里面的内容如下

 let code = readFileSync(resolve(filepath)).toString()
  //判断是.css结尾的文件
  if(/\.css$/.test(filepath)){
    code = `
     const str = ${JSON.stringify(code)}
     //如果document存在,就动态创建一个style标签插入到head里面
     if(document){   
       const style = document.createElement('style')
       style.innerHTML = str
       document.head.appendChild(style)
     }
     export default str
    `
  }
  //把读取到的es6代码先进行转换成es5的
  const { code: es5Code } = babel.transform(code, {
    presets: ['@babel/preset-env']
  })

测试

在当前项目下面新建project-css

新增index.js

console.log(12342423)
import './style.css'

新增style.css

body{
  color: rebeccapurple;
}

新增index.html

<!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>Document</title>
</head>
<body>
  <h1>Css加载成功</h1>
  <script src="dist.js"></script>
</body>
</html>

修改bundler_css.ts里面的入口文件出口文件

//设置项目根目录
const projectRoot = resolve(__dirname, 'project-css')

writeFileSync('./project-css/dist.js', generateCode())

运行以下命令

 npx ts-node bundler_css.ts

会在project-css文件夹下面生成dist.js文件。

image.png 我们在浏览查看index.html

image.png 如果你看到了当前的效果说明你的打包器现在已经支持打包.css文件啦。

创建CSS Loader

首先说一下

loader长什么样子?

  • 一个loader可以是一个普通函数
function transform(code){
  const code2 = doSomething(code)
  return code2
}
module.exports = transform // 用 module 是为了兼容 Node.js
  • 一个loader也可以是一个异步函数
async function transform(code){
  const code2 = await doSomething(code)
  return code2
}
module.exports = transform // 旧版本 Node.js 不支持 export 关键字

-中划线表示连接,_下划线表示间隔。

上面我们已经成功的让打包器支持了css文件,接着我们想,可不可以将处理css代码的这块逻辑分离出去呢?带着这个思路,我们继续开始。

+loaders/css-loader.js

const transform = code => `
const str = ${JSON.stringify(code)}
if (document) {
  const style = document.createElement('style')
  style.innerHTML = str
  document.head.appendChild(style)
}
export default str
`

module.exports = transform

拷贝一份之前的打包器文件,进行修改,如下,同样也可以实现之前的功能,不一样的地方就在于把css转换成js和把style代码加入到head里面的代码与打包器的代码进行了分离。仅此而已

 let code = readFileSync(resolve(filepath)).toString()
  //判断是.css结尾的文件
  if(/\.css$/.test(filepath)){
    code = require('./loaders/css-loader.js')(code)
  }
  //把读取到的es6代码先进行转换成es5的
  const { code: es5Code } = babel.transform(code, {
    presets: ['@babel/preset-env']
  })

发现打包器的问题

单一职责原则

webpack里面的loader只做一件事情,现在我们的css-loader做了两件事情

  1. css转成了js字符串。
  2. js字符串放到了style标签里面。

所以我们试图把现在的css-loader拆成两个css-loaderstyle-loader

css-loader

const transform = code => code

module.exports = transform

style-loader

const transform = code => `
if (document) {
  const style = document.createElement('style')
  style.innerHTML = ${JSON.stringify(code)}
  document.head.appendChild(style)
}
`
module.exports = transform

我们检测到如果是.css结尾的就把文件内容当做字符串保存起来,接下来调用style-loader把这段字符串插入到页面里面就可以了。

 let code = readFileSync(resolve(filepath)).toString()
  //判断是.css结尾的文件
  if(/\.css$/.test(filepath)){
    code = require('./loaders/css-loader.js')(code)
    code = require('./loaders/style-loader.js')(code)
  }
  //把读取到的es6代码先进行转换成es5的
  const { code: es5Code } = babel.transform(code, {
    presets: ['@babel/preset-env']
  })

loader面试题

webpackloader是什么?

  1. webpack自带的打包功能支持打包js文件。
  2. 在我们项目中要想加载css/less/ts/md文件的时候,就需要用到loader
  3. loader的原理就是把文件内容包装成能够运行的js 比如
  4. 加载css的时候就需要用到css-loaderstyle-loader
  5. css-loader负责把css源代码变成export default strjs代码形式。
  6. style-loader负责把源码挂载到head里面的style标签里

系列文章

Webpack入门到精通 一(AST、Babel、依赖)

Webpack入门到精通 二(核心原理)

Webpack入门到精通 三(Loader原理)

Webpack入门到精通 四(Plugin原理)

Webpack入门到精通 五(常用配置)