前言:阅读本文,你将学会用webpack5构建一个简单的项目,手写一个style-loader,以及手写一个plugin。无论是满足对webpack5的好奇心,还是面试都会有帮助的,希望大家一起动手写起来吧!
前端构建初体验
webpack5及相关工具安装
目录创建:
- mkdir webpack-demo
- cd webpack-demo
- npm init
webpack&webpack-cli
- install:npm i webpack webpack-cli -D
- check: 检查./node_modules/目录下是否有webpack
构建一个最简单的前端项目
目标:
- html、js、css等资源正常输出
- 构建的结果可以在浏览器跑起来
安装插件:
- npm i html-webpack-plugin -D
- npm i css-loader style-loader -D
- 首先在src目录下创建index.html、index.css、index.js文件
- 配置webpack.config.js
- 在package.json文件中增加build命令: "build": "webpack",
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>
<link rel="stylesheet" href="./index.css">
<script src="./index.js"></script>
</head>
<body>
</body>
</html>
index.js
import './index.css'
console.log('this is demo')
index.css
body{
height: 100px;
background-color: blue;
}
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
module.exports = {
entry: './src/index.js',
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}]
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './src/index.html')
})
]
}
运行npm run build 构建完会打包一个dist目录 再打开dist目录下的index.html文件,样式就生效了,ok第一个小目标完成 (ᕑᗢᓫ∗)˒
如何实现style-loader?
- 创建loader/loader1.js
- 在pitch方法有三个参数:remainingRequest:未执行的loader,previousRequest:已经执行的loader,data:用来和loader方法交互;
- 把样式文件插入到html,loader方法中的sourc不能直接用,所以得在pitch中执行;
- stringifyRequest来处理remainingRequest路径
- npm i loader-utils@1.2.3, ps:不知道为啥最新版没有stringifyRequest方法;
- return 一段js代码,创建一个style标签,获取路径的内容,判断内容是否是esModule,是的话取default,将content插到head中;
const { stringifyRequest } = require('loader-utils')
function loader(source){
return source
}
loader.pitch = function(remainingRequest, previousRequest, data){
console.log(11, remainingRequest, 22, previousRequest, 33, data)
const modulePath = stringifyRequest(this, `!!${remainingRequest}`)
return `
var element = document.createElement('style');
var content = require(${modulePath});
content = content.__esModule ? content.default : content;
element.innerHTML = content;
var parentEle = document.querySelector('head');
parentEle.appendChild(element)
`
}
module.exports = loader
接下来是配置webpack.config文件:
resolveLoader: {
alias: {
'loader1': path.resolve(__dirname, './loader/loader1')
}
},
module: {
rules: [{
test: /\.css$/,
use: [
'loader1',
'css-loader'
]
}]
},
运行npm run build,打开dist/index.html,也有样式了(◕ˇ∀ˇ◕。)
如何实现一个plugin插件?
目标:
- 实现一个CSP策略的插件
- CSP: 内容安全策略,白名单机制
接下来是我们的实践过程:
- 首先创建plugins/test-plugin.js文件
// 依赖HtmlWebpackPlugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
function CSPPlugin(options){
this.options = options
}
CSPPlugin.prototype.apply = function(compiler){
console.log(111)
// 跟着HtmlWebpackPlugin去调用插件
compiler.hooks.thisCompilation.tap('CSPPlugin', (compilation) => {
// html处理完的时机
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync
('CSPPlugin', (data, cb) => {
console.log(data.html)
cb(null, data)
})
})
}
module.exports = {
CSPPlugin
}
代码14行的data.html打印出来如下图:
- 接着去完成plugin.apply方法的内容:
CSPPlugin.prototype.apply = function(compiler){
// 跟着HtmlWebpackPlugin去调用插件
compiler.hooks.thisCompilation.tap('CSPPlugin', (compilation) => {
// html处理完的时机
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync
('CSPPlugin', (data, cb) => {
// 插入一个meta标签,需要加一个http-equiv属性
const insertHtml = `<meta http-equiv="Content=Security-Policy"
content=default-src 'self'">`
// 找到html对应的插入位置
const index = data.html.indexOf('<head>')
// +6是略过<head>标签
data.html = data.html.substring(0, index + 6) + insertHtml +
data.html.substring(index + 6)
cb(null, data)
})
})
}
- 接着来配置webpack.config文件: 引入我们自定的plugin:
const { CSPPlugin } = require('./plugins/test-plugin')
- 在plugins中配置:
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './src/index.html')
}),
new CSPPlugin({})
]
运行npm run build命令,查看dist/index.html文件已经插入了meta标签(˶˚ ᗨ ˚˶)