Webpack-Plugin
TIP
所谓 loader 只是一个导出为函数的 JavaScript 模块。它接收上一个 loader 产生的结果或者资源文件(resource file)作为入参。也可以用多个 loader 函数组成 loader chain。compiler 需要得到最后一个 loader 产生的处理结果。这个处理结果应该是 String 或者 Buffer(被转换为一个 string)
一 运行流程
loader-runner是一个执行loader链条的的模块
二 loader-runner简介
- webpack的loader是通过loader-runner串联起来的
- webpack-loader-api
- loader-runnder官方
- loader-runner是一个执行loader链条的的模块
1 类型
四大类型
- post: 后置
- inline: 内联
- normal: 普通
- pre: 前置
执行优先级:pre > normal > inline > post
叠加顺序为:post(后置) + inline(内联) + normal(普通) + pre(前置)
2 特殊符号
符号 | 变量 | 含义 |
---|---|---|
-! | noPreAutoLoaders | 不要前置和普通 loader |
! | noAutoLoaders | 不要普通 loader |
!! | noPrePostAutoLoaders | 不要前置、后置和普通 loader,只要内联 loader |
3 执行顺序
- pitch 从左往右执行
- loader 从下往上,从右往左执行
4 pitch
- 每个loader都有一个pitch方法,但pitch不是必须的
- pitch两个作用:阻断 loader 链 和 存放自身需要的额外数据
- pitch方法的三个参数:
- remainingRequest:后面的loader+资源路径,loadername!的语法
- precedingRequest:资源路径
- metadata:辅助对象,用于存放自身需要的额外数据
- loader中的this表示上下文,可以访问很多属性和方法
// loader方法
function loader(source) {
// 获取pitch中定义的data对象
console.log(this.data.name);
return source;
}
/**
* pitch方法,是非必须的
* 后面的loader等都可以拿到data中的值
* 每一个loader都有一个自己的data,相互之间是完全 独立的
*/
loader.pitch = function(remainingRequest,previousRequest,data){
data.name = 'loader-pitch';
}
module.exports = loader;
- 有pitch的loader执行过程
// use配置loader
use: ['loader1', 'loader2', 'loader3']
// 执行过程
|- loader1 `pitch`
|- loader2 `pitch`
|- loader3 `pitch`
|- requested module is picked up as a dependency
|- loader3 normal execution
|- loader2 normal execution
|- loader1 normal execution
- 阻断loader链条:在 loader2 的 pitch 中返回了一个字符串,执行流程如下:
三 loader-runner使用
# webpack内置了loader runnder,安装了webpack可以直接使用
npm i webpack webpack-cli -D
1 预制八个loader
- post-loader1.js
function loader(source) {
console.log('post1');
return source + '//post1';
}
module.exports = loader;
- post-loader2.js
function loader(source) {
console.log('post2');
return source + '//post2';
}
module.exports = loader;
- inline-loader1.js
function loader(source) {
console.log('inline1');
return source + '//inline1';
}
module.exports = loader;
- inline-loader2.js
function loader(source) {
console.log('inline2');
return source + '//inline2';
}
module.exports = loader;
- normal-loader1.js
function loader(source) {
console.log('normal1');
return source + '//normal1';
}
module.exports = loader;
- normal-loader2.js
function loader(source) {
console.log('normal2');
return source + '//normal2';
}
module.exports = loader;
- pre-loader1.js
function loader(source) {
console.log('pre1');
return source + '//pre1';
}
module.exports = loader;
- pre-loader2.js
function loader(source) {
console.log('pre2');
return source + '//pre2';
}
module.exports = loader;
2 直接使用
let path = require('path');
let fs = require('fs');
let {runLoaders} = require('loader-runner');
// 要读取的资源文件
let resource = path.resolve(__dirname, 'src', 'index.js');
// loader数组
let loaders = [
path.resolve(__dirname, 'loaders', 'post-loader1.js'),
path.resolve(__dirname, 'loaders', 'post-loader2.js'),
path.resolve(__dirname, 'loaders', 'inline-loader1.js'),
path.resolve(__dirname, 'loaders', 'inline-loader2.js'),
path.resolve(__dirname, 'loaders', 'normal-loader1.js'),
path.resolve(__dirname, 'loaders', 'normal-loader2.js'),
path.resolve(__dirname, 'loaders', 'pre-loader1.js'),
path.resolve(__dirname, 'loaders', 'pre-loader2.js'),
];
/**
* 1.读取要加载的资源
* 2.把资源传递给loader链条,一一处理,最后得到结果
*/
runLoaders({
// 要加载和转化的资源,可以包含查询字符串
resource,
// loader的绝对路径数组
loaders,
// 额外的loader上下文对象,即loader里面的this指向
context: {name: 'test'},
// 读取文件的方法
readResource: fs.readFile.bind(fs)
}, function(err, result) {
console.log(err);
console.log(result);
})
3 执行结果
pre2
pre1
normal2
normal1
inline2
inline1
post2
post1
null
{ result:
[ 'console.log(\'index\');\r\n//pre2//pre1//normal2//normal1//inline2//inline1//post2//post1' ],
resourceBuffer: <Buffer 63 6f 6e 73 6f 6c 65 2e 6c 6f 67 28 27 69 6e 64 65 78 27 29 3b 0d 0a>,
cacheable: true,
fileDependencies:
[ 'd:\\web_project\\loader\\src\\index.js' ],
contextDependencies: [],
missingDependencies: []
}
四 loader-runner原理
1 类型与特殊符号
// 引入依赖模块
let path = require('path');
let fs = require('fs');
let {runLoaders} = require('loader-runner');
let filePath = path.resolve(__dirname, 'src', 'index.js');
// 内联loader
let request = `inline-loader1!inline-loader2!${filePath}`;
// 先替换特殊符号,然后切割获取资源名称
let parts = request.replace(/^-?!+/,'').split('!');
// 最后一个元素就是要加载的资源了
let resource = parts.pop();
// 变为绝对路径
let resolveLoader = loader => path.resolve(__dirname, 'loaders', loader);
// inlineLoaders=[inline-loader1绝对路径, inline-loader2绝对路径 ]
let inlineLoaders = parts.map(resolveLoader);
let rules = [
{
test:/\.js$/,
use:['normal-loader1','normal-loader2']
},
{
test:/\.js$/,
enforce:'post',//post webpack保证一定是后执行的
use:['post-loader1','post-loader2']
},
{
test:/\.js$/,
enforce:'pre',//一定先执行eslint
use:['pre-loader1','pre-loader2']
},
];
// 解析webpack配置的rules中的loader
let preLoaders = [];
let postLoaders = [];
let normalLoaders = [];
for (let i = 0; i < rules.length; i++) {
let rule = rules[i];
if (rule.test.test(resource)) {
if (rule.enforce == 'pre') {
preLoaders.push(...rule.use);
} else if (rule.enforce == 'post') {
postLoaders.push(...rule.use);
} else {
normalLoaders.push(...rule.use);
}
}
}
preLoaders = preLoaders.map(resolveLoader);
postLoaders = postLoaders.map(resolveLoader);
normalLoaders = normalLoaders.map(resolveLoader);
// 按照顺序组装loader:post(后置) + inline(内联) + normal(正常) + pre(前置)
let loaders = [];
if (request.startsWith('!!')) {
// 不要前后置和普通 loader,只要内联 loader
loaders = [...inlineLoaders];
} else if (request.startsWith('-!')) {
// 不要前置和普通 loader
loaders = [...postLoaders,...inlineLoaders];
} else if (request.startsWith('!')) {
// 不要普通 loader
loaders = [...postLoaders,...inlineLoaders,...preLoaders];
} else {
// 全部loader
loaders = [...postLoaders,...inlineLoaders,...normalLoaders,...preLoaders];
}
/**
* 1.读取要加载的资源
* 2.把资源传递给loader链条,一一处理,最后得到结果
*/
runLoaders({
// 要加载和转化的资源,可以包含查询字符串
resource,
// loader的绝对路径数组
loaders,
// 额外的loader上下文对象
context: {name: 'test'},
// 读取文件的方法
readResource: fs.readFile.bind(fs)
}, function(err, result) {
console.log(err);
console.log(result);
})
2 loader-runner实现
五 loader 案例
- webpack配置本地loader的三种方式
- 在resolveLoader里配置alias别名
module.exports = {
resolveLoader:{
alias:{
'babel-loader':path.resolve('./loaders/babel-loader.js')
}
},
module:{
rules:[
{
test: /\.js$/,
use: 'babel-loader'
}
]
}
}
- 在resolveLoader里配置modules别名
module.exports = {
resolveLoader:{
modules: [path.resolve('./loaders'), 'node_modules']
},
module:{
rules:[
{
test: /\.js$/,
use: 'babel-loader'
}
]
}
}
- 在配置 rules 的时候直接指定 loader 的绝对路径
module.exports = {
module:{
rules:[
{
test: /\.js$/,
use: [path.resolve('./loaders/babel-loader.js')],
include: path.resolve('src')
}
]
}
}
1 babel-loader
npm i @babel/preset-env @babel/core -D
- loaders/babel-loader.js
const core = require('@babel/core');
/**
* ES6 转化为 ES5
* @param {*} source 上一个loader给我这个loader的内容或者最原始模块内容
* @param {*} inputSourceMap 上一个loader传递过来的sourceMap
*/
function loader(source, inputSourceMap) {
const options = {
presets: ['@babel/preset-env'],
inputSourceMap,
sourceMap: true, // 告诉babel要生成sourceMap
filename: path.basename(this.resourcePath) // /src/index.js
}
// code:转换后的代码 map:sourcemap ast:抽象语法树
let {code, map, ast} = core.transform(source, options);
/**
* 当你需要返回多值的时候需要使用 this.callback来传递多个值
* 只需要返回一个值,可以直接 return
* map 可以让我们进行代码调试 debug的时候可以看到源代码
* ast 如果你返回了ast给webpack。webpack则直接分析 就可以,不需要自己转AST了,节约 时间
*/
return this.callback(null, code, map, ast);
}
module.exports = loader;
- webpack.config.js
const path = require('path');
module.exports = {
module:{
rules:[
{
test: /\.js$/,
use: [path.resolve('./loaders/babel-loader.js')],
include: path.resolve('src')
}
]
}
}
2 file-loader
loaders/file-loader.js
const path = require('path');
const {getOptions,interpolateName} = require('loader-utils');
/**
* 负责把资源文件拷贝到对应的目录,不做文件转换,只会生成一个唯一的文件名
* 1.把此文件内容拷贝到目标目录里
* 2. 生成一个唯一的hash文件名
* @param {*} content 内容
*/
function loader(content) {
// 获取在loader中配置的参数对象
let options = getOptions(this) || {};
// 根据 options.name 以及文件内容生成一个唯一的文件名
let filename = interpolateName(this, options.name || '[contenthash].[ext]', {content});
// 输出文件,webpack 会根据参数创建对应的文件,放在 public path 目录下
this.emitFile(filename, content);
// 根据不同的配合导出不同的模块
if (typeof options.esModule === 'undefined' || options.esModule) {
// es modules 》 使用需要加default
return `export default "${filename}"`;
} else {
// commonjs 》直接使用
return `module.exports = ${JSON.stringify(filename)}`;
}
}
// 二进制格式
loader.raw = true;
module.exports = loader;
- webpack.config.js
const path = require('path');
module.exports = {
module:{
rules:[
{
test:/\.(jpg|png|gif)$/,
use:[{
loader:path.resolve('./loaders/file-loader.js'),
options:{
name:'[hash:8].[ext]',
esModule: false
}
}],
include:path.resolve('src')
}
]
}
}
3 url-loader
npm i mime -D
- loaders/url-loader.js
const mime = require('mime');
const {getOptions} = require('loader-utils');
/**
* 对file-loader的加强,根据配置的参数,来看判断是否需要将图片资源变为base64内嵌到文件中,减少请求
* @param {*} content 内容
*/
function loader(content) {
// 获取在loader中配置的参数对象
let options = getOptions(this) || {};
// 解析限制参数
let {limit=8*1024, fallback="file-loader"} = options;
// 获取文件的类型
const mimeType = mime.getType(this.resourcePath);//image/jpeg
// 判断文件大小,输出不同内容
if (content.length < limit) {
// 转成base64内嵌
let base64Str = `data:${mimeType};base64,${content.toString('base64')}`;
return `module.exports = ${JSON.stringify(base64Str)}`;
} else {
// 走file-loader的逻辑
let fileLoader = require(fallback);
return fileLoader.call(this, content);
}
}
// 二进制格式
loader.raw = true;
module.exports = loader;
- webpack.config.js
const path = require('path');
module.exports = {
module:{
rules:[
{
test:/\.(jpg|png|gif)$/,
use:[{
loader:path.resolve('./loaders/url-loader.js'),
options:{
name:'[hash:8].[ext]',
limit:60*1024,
fallback:path.resolve('./loaders/file-loader.js')
}
}],
include:path.resolve('src')
}
]
}
}
4 less-loader
npm i less -D
- loaders/less-loader.js
const less = require('less');
/**
* 把LESS编译成CSS字符串
* @param {*} content 内容
*/
function loader(content) {
// 默认情况下loader执行是同步的
// 通过调用this.async方法可以返回一个函数,会把loader的执行变成异步的,不会直接往下执行了
let callback = this.async();
// less 转化
less.render(content, {filename: this.resource}, (err, output) => {
// 会让loader继续往下执行
callback(err, output.css);
});
}
module.exports = loader;
- webpack.config.js
const path = require('path');
module.exports = {
module:{
rules:[
{
test:/\.less$/,
use:[
path.resolve('./loaders/less-loader.js')
],
include:path.resolve('src')
}
]
}
}
5 style-loader
loaders/style-loader.js
let loaderUtils = require('loader-utils');
/**
* 把CSS变成一个JS脚本
* 脚本就是动态创建一个style标签,并且把这个style标签插入到HTML里header
* 什么时候会用到pitch loader
* 当你想把两个最左侧的loader级联使用的时候
* @param {*} inputSource 内容
*/
function loader(inputSource) {}
loader.pitch = function(remainingRequest, previousRequest, data) {
// remainingRequest > 剩下的loader!要加载的路径
// !!只要行内样式
// !!./loaders/css-loader.js!./src/index.css
let style = `
let style = document.createElement('style');
style.innerHTML = require(${loaderUtils.stringifyRequest(this, "!!" + remainingRequest)}).toString();
document.head.appendChild(style);
`;
return style;
}
module.exports = loader;
- webpack.config.js
const path = require('path');
module.exports = {
module:{
rules:[
{
test:/\.less$/,
use:[
path.resolve('./loaders/style-loader.js')
],
include:path.resolve('src')
}
]
}
}
6 to-string-loader
loaders/to-string-loader.js
let loaderUtils = require('loader-utils');
/**
* 根据不同的配置输出字符串
*/
function loader(inputSource) {}
loader.pitch = function(remainingRequest, previousRequest, data) {
return `
let result = require(${loaderUtils.stringifyRequest(this, "!!" + remainingRequest)});
if(result && result.__esModule){
result = result.default;
}
if(typeof result === 'string'){
module.exports = result;
}else{
module.exports = result.toString();
}
`;
}
module.exports = loader;
- webpack.config.js
const path = require('path');
module.exports = {
module:{
rules:[
{
test:/\.css$/,
use:[
path.resolve('./loaders/to-string-loader.js')
],
include:path.resolve('src')
}
]
}
}
六. css-loader
- css-loader
- css-loader可以把@import and url()翻译成import/require(),然后可以解析处理它们
1 安装
npm i webpack webpack-cli webpack-dev-server html-webpack-plugin css-loader css-selector-tokenizer file-loader less less-loader postcss style-loader to-string-loader -D
2 使用
- webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode:'development',
devtool:false,
module:{
rules:[
{
test:/\.css$/,
use:[
"to-string-loader",
{
loader:'css-loader',
options:{
url:false,
import:false,
esModule: false
}
}
],
include:path.resolve('src')
}
]
},
plugins:[
new HtmlWebpackPlugin({template:'./src/index.html'}),
]
}
- src\index.js
const css = require("./global.css");
console.log(css);
- src\global.css
body {
background-color: green;
}
PS: 引用顺序为 index.js > global.css
- dist\main.js - 打包文件
(() => {
var __webpack_modules__ = ({
"./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./src/global.css":
((module, exports, require) => {
var api = require("./node_modules/css-loader/dist/runtime/api.js");
var EXPORT = api(function (i) {
return i[1]
});
EXPORT.push([
module.id,
"body {\r\n\tbackground-color: green;\r\n}",
""
]);
module.exports = EXPORT;
}),
"./node_modules/css-loader/dist/runtime/api.js": ((module) => {
module.exports = function (cssWithMappingToString) {
var list = [];
list.toString = function toString() {
return this.map(function (item) {
var content = cssWithMappingToString(item);
if (item[2]) {
return "@media ".concat(item[2], " {").concat(content, "}");
}
return content;
}).join('');
};
list.i = function (modules, mediaQuery, dedupe) {
if (typeof modules === 'string') {
modules = [[null, modules, '']];
}
var alreadyImportedModules = {};
if (dedupe) {
for (var i = 0; i < this.length; i++) {
var id = this[i][0];
if (id != null) {
alreadyImportedModules[id] = true;
}
}
}
for (var _i = 0; _i < modules.length; _i++) {
var item = [].concat(modules[_i]);
if (dedupe && alreadyImportedModules[item[0]]) {
continue;
}
if (mediaQuery) {
if (!item[2]) {
item[2] = mediaQuery;
} else {
item[2] = "".concat(mediaQuery, " and ").concat(item[2]);
}
}
list.push(item);
}
};
return list;
};
}),
"./src/global.css": ((module, exports, require) => {
var result = require("./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./src/global.css");
if (typeof result === "string") {
module.exports = result;
} else {
module.exports = result.toString();
}
})
});
var cache = {};
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports;
}
var module = cache[moduleId] = {
id: moduleId,
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, require);
return module.exports;
}
// 入口
(() => {
const css = require("./src/global.css")
console.log(css);
})();
})();
- 分析打包文件1
// 直接导出字符串给浏览器使用
(() => {
var __webpack_modules__ = ({
"./src/global.css": ((module, exports, require) => {
// 直接导出字符串
module.exports = "body {\r\n\tbackground-color: green;\r\n}";
})
});
var cache = {};
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports;
}
var module = cache[moduleId] = {
id: moduleId,
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, require);
return module.exports;
}
// 入口
(() => {
const css = require("./src/global.css")
console.log(css);
})();
})();
- 分析打包文件2
// 使用数组存储所有的引用模块,方便后续合并输出
(() => {
var __webpack_modules__ = ({
"./src/global.css": ((module, exports, require) => {
// 数组是为了保存多次引用@import,全部模块存储到一个数组中
var list = [];
list.push([
module.id, "body {\r\n\tbackground-color: green;\r\n}"
]);
// 一个映射函数,把每一个CSS描述对象转成CSS代码,取每个数组的索引为1的代码
let cssWithMappingToString = item => item[1];
let css = list.map(cssWithMappingToString).join("");
module.exports = css;
})
});
var cache = {};
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports;
}
var module = cache[moduleId] = {
id: moduleId,
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, require);
return module.exports;
}
// 入口
(() => {
const css = require("./src/global.css")
console.log(css);
})();
})();
- 分析打包文件3
// 使用css-loader重写加载一次global.css
(() => {
var __webpack_modules__ = ({
"css-loader.js!./src/global.css": ((module, exports, require) => {
var api = require("api.js");
// 一个映射函数,把每一个CSS描述对象转成CSS代码,取每个数组的索引为1的代码
let cssWithMappingToString = item => item[1];
let EXPORT = api(cssWithMappingToString);
EXPORT.push([
module.id, "body {\r\n\tbackground-color: green;\r\n}"
]);
module.exports = EXPORT ;
}),
"api.js": ((module) => {
module.exports = function(cssWithMappingToString) {
// 数组是为了保存多次引用@import,全部模块存储到一个数组中
var list = [];
// 重写tostring方法
list.toString = function() {
return this.map(cssWithMappingToString).join("");
}
return list;
}
}),
"./src/global.css": ((module,exports,require)=>{
var result = require("css-loader.js!./src/global.css");
module.exports = result.toString();
})
});
var cache = {};
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports;
}
var module = cache[moduleId] = {
id: moduleId,
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, require);
return module.exports;
}
// 入口
(() => {
const css = require("./src/global.css")
console.log(css);
})();
})();
3 源码
思想:遍历css的ast,找到import和url,然后分别利用各自规则进行处理
import:找到@import,先删除,然后放入一个数组中,最后利用剩余的loaders,进行require对应的css模块
url:找到url,替换为require(url)就行了
loaders/css-loader.js
let loaderUtils = require('loader-utils');
let postcss = require('postcss'); // css转为AST
let Tokenizer = require('css-selector-tokenizer'); // 节点转化为CSS脚本
function loader(inputSource) {
// 获取配置参数
let loaderOptions = loaderUtils.getOptions(this) || {};
const cssPlugin = (options) => {
return (root) => {
// 处理@import
if (loaderOptions.import) {
// 1.删除所有的@import 2.把导入的CSS文件路径添加到options.imports里
root.walkAtRules(/^import$/i, rule => {
// 在CSS脚本里把这@import删除
rule.remove();
// 截取css放入import中。类似./global.css
options.imports.push(rule.params.slice(1, -1));
});
}
// 处理url
if (loaderOptions.url) {
// 2.遍历语法树,找到里面所有的url
// 因为这个正则只能匹配属性
root.walkDecls(/^background-image/, decl => {
let values = Tokenizer.parseValues(decl.value);
values.nodes.forEach(node => {
node.nodes.forEach(item => {
if (item.type === 'url') {
// stringifyRequest可以把任意路径标准化为相对路径
let url = loaderUtils.stringifyRequest(this, item.url);
// 标识字符串类型为单引号
item.stringType = "'";
item.url = "`+require("+url+")+`";
// require会给webpack看和分析,webpack一看你引入了一张图片
// webpack会使用file-loader去加载图片
}
});
});
let value = Tokenizer.stringifyValues(values);
decl.value = value;
});
}
}
}
// 返回异步回调函数
let callback = this.async();
//将会用它来收集所有的@import
let options = { imports: [] };
let pipeline = postcss([cssPlugin(options)]);
// 处理源代码,返回promise
pipeline.process(inputSource).then(result => {
let {importLoaders = 0} = loaderOptions; // 获取配置的几个前置loader
let {loaders, loaderIndex} = this; // 所有的loader数组和当前loader的索引
let loaderRequest = loaders.slice(
loaderIndex,
loaderIndex + importLoaders + 1 // 截取不包含最后一位,所以需要+1
).map(x => x.request).join('!'); // request是loader绝对路径
// !css-loader.js的绝对路径!less-loader.js的绝对路径!./global.css
// stringifyRequest: 可以将绝对路径转化为相对路径的字符串(带引号)
let importCss = options.imports
.map(url => `list.push(...require(` +
loaderUtils.stringifyRequest(this, `-!${loaderRequest}!${url}`)
+ `))`)
.join('\r\n');
let script = `
var list = [];
list.toString = function(){return this.join('')};
${importCss}
list.push(\`${result.css}\`);
module.exports = list;
`;
// 异步返回结果
callback(null, script);
});
}
module.exports = loader;
- webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode:'development',
devtool:false,
resolveLoader: {
modules: [path.resolve(__dirname, 'loaders'), 'node_modules']
},
module:{
rules:[
{
test:/\.css$/,
use:[
"to-string-loader",
{
loader:'css-loader',
options:{
url: true, // 是否解析url()
import: true, // 是否解析@import语法
esModule: false, // 不包装成ES MODULE,默认是common.js导出
importLoaders: 0 // 在处理导入的CSS的时候,要经过几个前置loader的处理
}
}
],
include:path.resolve('src')
},
{
test:/\.(jpg|png|gif)/,
use:[
{
loader:"file-loader",
options:{
esModule: false
}
}
]
}
]
},
plugins:[
new HtmlWebpackPlugin({template:'./src/index.html'}),
]
}
- src目录
// src/index.js
const css = require("./index.css");
console.log(css);
// src/index.css
@import "./global.css";
body {
color: red;
}
#root {
background-image: url(./images/kf.jpg);
background-size: contain;
width: 100px;
height: 100px;
}
// src/global.css
@color:orange;
body{
background-color: @color;
}
PS: 引用顺序为 index.js > index.less > global.css
4 图解
七、PostCSS
- PostCSS 是一个用 JavaScript 工具和插件转换 CSS 代码的工具
- PostCSS官网
- PostCSS Api
- Astexplorer CSS可视化
1 工作机制
- PostCSS 会将CSS代码解析成包含一系列节点的抽象语法树(AST),树上每个节点都是CSS代码中每个属性的符号化表示
- AST被传递给后续的插件进行处理,处理完了之后转化为新的CSS代码
2 类型
- CSS AST 主要有3种父类型
- AtRule @xxx的这种类型,如@screen、 @import
- Comment 注释
- Rule 普通的css规则
- 子类型
- decl 指的是每条具体的css规则
- rule 作用于某个选择器上的css规则集合
3 AST节点
- nodes: CSS规则的节点信息集合
- decl: 每条css规则的节点信息
- prop: 样式名,如width
- value: 样式值,如10px
- type: 类型
- source: 包括start和end的位置信息,start和end里都有line和column表示行和列
- selector: type为rule时的选择器
- name: type为atRule时@紧接rule名,譬如@import 'xxx.css'中的import
- params: type为atRule时@紧接rule名后的值,譬如@import 'xxx.css'中的xxx.css
- text: type为comment时的注释内容
4 操作
- walk: 遍历所有节点信息,无论是atRule、rule、comment的父类型,还是rule、 decl的子类型
- walkAtRules:遍历所有的AtRules
- walkComments 遍历所有的Comments
- walkDecls 遍历所有的Decls
- walkRules 遍历所有的Rules
- postCss给出了很多操作CSS)的方法
- postcss插件如同babel插件一样,有固定的格式
- 注册个插件名,并获取插件配置参数opts
- 返回值是个函数,这个函数主体是你的处理逻辑,第一个参数是AST的根节点
5 示例
// 将px转化为rem的小插件
var postcss = require("postcss");
const cssPlugin = (options) => {
// CSS AST语法树的根节点
return (root,result) => {
//遍历所有的@Rule @import
root.walkAtRules();
// 遍历decls - 具体的css代码规则
root.walkDecls((decl) => {
// console.log(decl);
// 转化
if(decl.value.endsWith('px')){
decl.value = parseFloat(decl.value)/75 + 'rem';
}
});
};
};
let options = {};
// 管道
let pipeline = postcss([cssPlugin(options)]);
// 源代码
let inputSource = `
#root{
width:750px;
}`;
/**
* post内部
* 1 pipeline其实先把CSS源代码转成CSS抽象语法树
* 2 遍历语法树,让插件进行处理
*/
pipeline.process(inputSource).then((result) => {
console.log(result.css);
})