前言
基于上次实现的简易打包器,我们只需在获取文件的位置区分是js文件还是css文件,如果是css文件则走css相关逻辑。
核心代码:
let code = readFileSync(filepath).toString()
if(/\.css$/.test(filepath)){
code = `
const str = ${JSON.stringify(code)}
if(document){
const style = document.createElement('style')
style.innerHTML = str
document.head.appendChild(style)
}
export default str
`
}
const { code: es5Code } = babel.transform(code, {
presets: ['@babel/preset-env']
})
打造一个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-loader.js文件中,再引用css-loader.js:
// 获取文件内容,将内容放至 depRelation
let code = readFileSync(filepath).toString()
if(/\.css$/.test(filepath)){ // 如果文件路径以 .css 结尾
// 就把css改造成js,并自动加载到head里
// 用require而不用import主要是为了方便动态加载
code = require('./loaders/css-loader.js')(code)
}
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-loader.js,其实是做了两件事情:
- 将CSS导出为js
- 再将CSS代码在指定时间插入到指定代码中
我们按道理最好将这两步分成两个loader,这样更易于后期的扩展。
css.loader
const code = code => `
const str = ${JSON.stringify(code)}
export default str
`
module.exports = code
style.loader
import code form css.loader
const transform = code => `
if(document){
const style = document.createElement('style')
style.innerHTML = code
document.head.appendChild(style)
}
`
module.exports = transform
但是这样会有问题,问题在哪?
style-loader 不是转译
sass-loader、less-loader 这些 loader 是把代码从一种语言转译为另一种,因此将这样的 loader 连接起来不会出问题。
但 style-loader 是在插入代码,不是转译,所以需要寻找插入时机和插入位置,插入代码的时机应该是在获取到 css-loader 的结果之后,插入代码的位置应该是在旧代码的下面。
遇到困难,查看Webpack源码是如何做的
Webpack 官方 style-loader(源码) 的思路:
- style-loader 在 pitch 钩子里通过 css-loader 来 require 文件内容
- 然后在文件内容后面添加 injectStylesIntoStyleTag(content, ...) 代码
- 官方style.loader比我们的style.loader多了一个传参request,这样用户可以传入自定义事件,保证插入时间以及方式。
loaderApi.pitch = function loader(request) {......}
loader总结
- webpack 自带的打包器只能支持 JS 文件
- 当我们想要加载 css/less/scss/stylus/ts/md 文件时,就需要用 loader loader 的原理就是把文件内容包装成能运行的 JS
- 例如加载 css 需要用到 style-loader 和 css-loader,
- css-loader 把代码从 CSS 代码变成 export default code 形式的 JS 代码
- style-loader 把代码挂载到 head 里的 style 标签里