确保 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();