确保 webpack 插件在编译前完成
Make sure webpack plugin finishes before compilation
我正在尝试编写一个 webpack 插件(我的第一个!所以请耐心等待)下载 fontello 图标字体,将它们放在 dist 文件夹中,然后生成一个 icons.scss 文件,其中包含 SASS 每个图标的变量。
我想这有点不合常规,因为 icons.scss 文件不应该放在 dist/ 文件夹中,而应该放在 src/ 文件夹中。不过,将文件放在 src/ 中似乎并非不可能,而且它需要在那里才能让我的 app.scss 导入它。
我遇到的问题是我生成的 icons.scss 文件(包含在 SASS (app.scss) 的主入口点)没有时间在编译 SASS 之前生成。
有什么方法可以让 webpack 等到我的插件完成后再继续构建吗?
需要说明的是,我的一切都运行良好,下载字体,生成 icons.scss 等,唯一的问题是 SASS 在 icons.scss 存在之前编译。我也不知道哪个 compiler/compilation 钩子最适合使用,所以我也很想在这方面提供一些意见。
请参阅此代码摘录以了解相关部分。我在我觉得事情也可以改进的地方留下了评论:
// Fontello API endpoint
let url = 'http://fontello.com';
// Read our fontello config JSON
let fontelloConfig = fs.createReadStream('icons.json', 'utf8');
// Send fontello config to API
request.post({url: url, formData: {config: fontelloConfig}}, (err, res, body) => {
if (err) {
return console.error(err);
}
// Fetch ZIP using the session we got back from the previous call
request.get(`http://fontello.com/${body}/get`)
// Unzip it
.pipe(unzipper.Parse())
// For each file
.on('entry', (entry) => {
// Get basename and extension
const basename = path.basename(entry.path);
const ext = path.extname(basename);
// Copy the fontello.css to the sass path
// NOTE: All of this is a mess, I'm writing the fontello.css file to src/icons.scss, then reading src/icons.scss
// only so that I can make my changes to its contents, finally I write the changed contents back to src/icons.scss
// please help me improve this part, I'm a node and webpack noob
if (basename === 'fontello.css') {
// Write fontello.css to icons.scss
entry.pipe(fs.createWriteStream('src/icons.scss')).on('finish', () => {
// Read the file...
fs.readFile('src/icons.scss', 'utf8', (err, data) => {
// NOTE: Not until this file is created should webpack continue with compilation,
// if this file doesn't exist when SASS is compiled it will fail because my app.scss is trying to import this file
fs.writeFile('src/icons.scss', makeMyChangesToTheFontelloCssFileContent(data), 'utf8', (err) => {});
});
});
}
// Copy fonts and config.json to dist
// NOTE: I'm a noob so I didn't know you're supposed to use compilation.assets[filename] = filecontent;
// I'm working on it, but please let me know if it's an easy change?
else if (entry.type === 'File' && (basename === 'config.json' || entry.path.indexOf('/font/') !== -1)) {
entry.pipe(fs.createWriteStream('dist/' + basename));
}
// Otherwise clean up(?): https://github.com/ZJONSSON/node-unzipper#parse-zip-file-contents
else {
entry.autodrain();
}
});
});
编辑:我研究了文档,但发现如果没有示例很难知道该怎么做。我已经在几乎每个编译器和编译挂钩上设置了回调,以了解它们何时以及如何运行 运行 但它并没有真正帮助我很多。
我对这个解决方案不满意,但我最终通过更改 package.json:
在 webpack 之前 运行 我的 fontello 脚本
"dev": "node fontello.js && webpack --mode development",
"build": "node fontello.js && webpack --mode production",
最后的 fontello.js 看起来像这样:
// Utils
const path = require('path');
const fs = require('fs');
const request = require('request');
const unzipper = require('unzipper');
const css = require('css');
// Fontello Plugin
class FontelloSassWebpackPlugin {
constructor (config) {
this.config = Object.assign({
src: path.resolve(__dirname, 'src/icons.json'),
dest: path.resolve(__dirname, 'src/assets/fontello'),
sass: path.resolve(__dirname, 'src/sass/icons.scss'),
url: 'http://fontello.com'
}, config);
}
// Converts the fontello.css to what we need
convertFontelloCss (code) {
var obj = css.parse(code);
var newRules = [];
var sassVars = '';
var sassMixin = '';
obj.stylesheet.rules.forEach(rule => {
const selector = (rule.selectors && rule.selectors.length) ? rule.selectors[0] : null;
if (selector) {
// [class] rule
if (selector.indexOf('[class^="icon-"]:before') !== -1) {
rule.selectors.push(...['[class^="icon-"]:after', '[class*=" icon-"]:after']);
rule.declarations.forEach(d => {
if (d.type === 'declaration') {
sassMixin += `${d.property}: ${d.value};\n`;
}
});
sassMixin = `@mixin icon ($icon-code: "[NOICO]") {\n${sassMixin}\ncontent: $icon-code;\n}`;
}
// Icon rule
if (selector.indexOf('.icon-') !== -1) {
const iconName = selector.match(/\.icon-(.*?):before/)[1];
var iconVal = '[NO-ICON]';
rule.declarations.forEach(d => {
if (d.property === 'content') {
iconVal = d.value;
}
});
newRules.push({
type: 'rule',
selectors: [`.icon-${iconName}.icon--after:before`],
declarations: [{
type: 'declaration',
property: 'content',
value: 'normal'
}]
});
newRules.push({
type: 'rule',
selectors: [`.icon-${iconName}.icon--after:after`],
declarations: rule.declarations
});
sassVars += `$icon-${iconName}: ${iconVal};\n`;
}
}
});
obj.stylesheet.rules.push(...newRules);
return css.stringify(obj, {compress: false}).replace(/\.\.\/font\//g, 'assets/fontello/') + sassMixin + sassVars;
}
apply () {
const fontelloConfig = fs.createReadStream(this.config.src, 'utf8');
// Make sure folder exists
if (!fs.existsSync(this.config.dest)) {
fs.mkdirSync(this.config.dest, {recursive: true});
}
// Fetch session
request.post({url: this.config.url, formData: {config: fontelloConfig}}, (err, res, body) => {
if (err) {
return console.error(err);
}
// Fetch ZIP
request.get(`${this.config.url}/${body}/get`)
// Unzip it
.pipe(unzipper.Parse())
// For each file
.on('entry', (entry) => {
const basename = path.basename(entry.path);
const ext = path.extname(basename);
// Copy the fontello.css to the sass path
if (basename === 'fontello.css') {
entry.pipe(fs.createWriteStream(this.config.sass)).on('finish', () => {
fs.readFile(this.config.sass, 'utf8', (err, data) => {
fs.writeFile(this.config.sass, this.convertFontelloCss(data), 'utf8', (err) => {});
});
});
}
// Copy fonts and config.json to dist
else if (entry.type === 'File' && (basename === 'config.json' || entry.path.indexOf('/font/') !== -1)) {
entry.pipe(fs.createWriteStream(this.config.dest + '/' + basename));
}
// Otherwise clean up: https://github.com/ZJONSSON/node-unzipper#parse-zip-file-contents
else {
entry.autodrain();
}
});
});
}
}
const fswp = new FontelloSassWebpackPlugin();
fswp.apply();
我正在尝试编写一个 webpack 插件(我的第一个!所以请耐心等待)下载 fontello 图标字体,将它们放在 dist 文件夹中,然后生成一个 icons.scss 文件,其中包含 SASS 每个图标的变量。
我想这有点不合常规,因为 icons.scss 文件不应该放在 dist/ 文件夹中,而应该放在 src/ 文件夹中。不过,将文件放在 src/ 中似乎并非不可能,而且它需要在那里才能让我的 app.scss 导入它。
我遇到的问题是我生成的 icons.scss 文件(包含在 SASS (app.scss) 的主入口点)没有时间在编译 SASS 之前生成。
有什么方法可以让 webpack 等到我的插件完成后再继续构建吗?
需要说明的是,我的一切都运行良好,下载字体,生成 icons.scss 等,唯一的问题是 SASS 在 icons.scss 存在之前编译。我也不知道哪个 compiler/compilation 钩子最适合使用,所以我也很想在这方面提供一些意见。
请参阅此代码摘录以了解相关部分。我在我觉得事情也可以改进的地方留下了评论:
// Fontello API endpoint
let url = 'http://fontello.com';
// Read our fontello config JSON
let fontelloConfig = fs.createReadStream('icons.json', 'utf8');
// Send fontello config to API
request.post({url: url, formData: {config: fontelloConfig}}, (err, res, body) => {
if (err) {
return console.error(err);
}
// Fetch ZIP using the session we got back from the previous call
request.get(`http://fontello.com/${body}/get`)
// Unzip it
.pipe(unzipper.Parse())
// For each file
.on('entry', (entry) => {
// Get basename and extension
const basename = path.basename(entry.path);
const ext = path.extname(basename);
// Copy the fontello.css to the sass path
// NOTE: All of this is a mess, I'm writing the fontello.css file to src/icons.scss, then reading src/icons.scss
// only so that I can make my changes to its contents, finally I write the changed contents back to src/icons.scss
// please help me improve this part, I'm a node and webpack noob
if (basename === 'fontello.css') {
// Write fontello.css to icons.scss
entry.pipe(fs.createWriteStream('src/icons.scss')).on('finish', () => {
// Read the file...
fs.readFile('src/icons.scss', 'utf8', (err, data) => {
// NOTE: Not until this file is created should webpack continue with compilation,
// if this file doesn't exist when SASS is compiled it will fail because my app.scss is trying to import this file
fs.writeFile('src/icons.scss', makeMyChangesToTheFontelloCssFileContent(data), 'utf8', (err) => {});
});
});
}
// Copy fonts and config.json to dist
// NOTE: I'm a noob so I didn't know you're supposed to use compilation.assets[filename] = filecontent;
// I'm working on it, but please let me know if it's an easy change?
else if (entry.type === 'File' && (basename === 'config.json' || entry.path.indexOf('/font/') !== -1)) {
entry.pipe(fs.createWriteStream('dist/' + basename));
}
// Otherwise clean up(?): https://github.com/ZJONSSON/node-unzipper#parse-zip-file-contents
else {
entry.autodrain();
}
});
});
编辑:我研究了文档,但发现如果没有示例很难知道该怎么做。我已经在几乎每个编译器和编译挂钩上设置了回调,以了解它们何时以及如何运行 运行 但它并没有真正帮助我很多。
我对这个解决方案不满意,但我最终通过更改 package.json:
在 webpack 之前 运行 我的 fontello 脚本"dev": "node fontello.js && webpack --mode development",
"build": "node fontello.js && webpack --mode production",
最后的 fontello.js 看起来像这样:
// Utils
const path = require('path');
const fs = require('fs');
const request = require('request');
const unzipper = require('unzipper');
const css = require('css');
// Fontello Plugin
class FontelloSassWebpackPlugin {
constructor (config) {
this.config = Object.assign({
src: path.resolve(__dirname, 'src/icons.json'),
dest: path.resolve(__dirname, 'src/assets/fontello'),
sass: path.resolve(__dirname, 'src/sass/icons.scss'),
url: 'http://fontello.com'
}, config);
}
// Converts the fontello.css to what we need
convertFontelloCss (code) {
var obj = css.parse(code);
var newRules = [];
var sassVars = '';
var sassMixin = '';
obj.stylesheet.rules.forEach(rule => {
const selector = (rule.selectors && rule.selectors.length) ? rule.selectors[0] : null;
if (selector) {
// [class] rule
if (selector.indexOf('[class^="icon-"]:before') !== -1) {
rule.selectors.push(...['[class^="icon-"]:after', '[class*=" icon-"]:after']);
rule.declarations.forEach(d => {
if (d.type === 'declaration') {
sassMixin += `${d.property}: ${d.value};\n`;
}
});
sassMixin = `@mixin icon ($icon-code: "[NOICO]") {\n${sassMixin}\ncontent: $icon-code;\n}`;
}
// Icon rule
if (selector.indexOf('.icon-') !== -1) {
const iconName = selector.match(/\.icon-(.*?):before/)[1];
var iconVal = '[NO-ICON]';
rule.declarations.forEach(d => {
if (d.property === 'content') {
iconVal = d.value;
}
});
newRules.push({
type: 'rule',
selectors: [`.icon-${iconName}.icon--after:before`],
declarations: [{
type: 'declaration',
property: 'content',
value: 'normal'
}]
});
newRules.push({
type: 'rule',
selectors: [`.icon-${iconName}.icon--after:after`],
declarations: rule.declarations
});
sassVars += `$icon-${iconName}: ${iconVal};\n`;
}
}
});
obj.stylesheet.rules.push(...newRules);
return css.stringify(obj, {compress: false}).replace(/\.\.\/font\//g, 'assets/fontello/') + sassMixin + sassVars;
}
apply () {
const fontelloConfig = fs.createReadStream(this.config.src, 'utf8');
// Make sure folder exists
if (!fs.existsSync(this.config.dest)) {
fs.mkdirSync(this.config.dest, {recursive: true});
}
// Fetch session
request.post({url: this.config.url, formData: {config: fontelloConfig}}, (err, res, body) => {
if (err) {
return console.error(err);
}
// Fetch ZIP
request.get(`${this.config.url}/${body}/get`)
// Unzip it
.pipe(unzipper.Parse())
// For each file
.on('entry', (entry) => {
const basename = path.basename(entry.path);
const ext = path.extname(basename);
// Copy the fontello.css to the sass path
if (basename === 'fontello.css') {
entry.pipe(fs.createWriteStream(this.config.sass)).on('finish', () => {
fs.readFile(this.config.sass, 'utf8', (err, data) => {
fs.writeFile(this.config.sass, this.convertFontelloCss(data), 'utf8', (err) => {});
});
});
}
// Copy fonts and config.json to dist
else if (entry.type === 'File' && (basename === 'config.json' || entry.path.indexOf('/font/') !== -1)) {
entry.pipe(fs.createWriteStream(this.config.dest + '/' + basename));
}
// Otherwise clean up: https://github.com/ZJONSSON/node-unzipper#parse-zip-file-contents
else {
entry.autodrain();
}
});
});
}
}
const fswp = new FontelloSassWebpackPlugin();
fswp.apply();