上一篇文章介绍了 css-loader 对 css 文件的编译,但是编译后的代码并没有应用到 DOM 中,所以还需要style-loader把css-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具体代码实现如下:
- 创建style标签
function insertStyleElement(options) {
var element = document.createElement('style')
return element
}
- 生成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-loader 和 css-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')
}