确保Webpack插件在编译之前完成

时间:2019-11-28 17:57:09

标签: node.js webpack webpack-plugin

我正在尝试编写一个webpack插件(我的第一个!请耐心等待),该插件下载fontello图标字体,将它们放在dist文件夹中,然后生成一个icons.scss文件,其中包含每个图标的SASS变量。 / p>

我猜这有点不合常规,因为icons.scss文件不应该放在dist /文件夹中,而应该放在src /文件夹中。不过,将文件放在src /中似乎并不是不可能的,并且它必须存在以便我的app.scss导入它。

我遇到的问题是,生成的icons.scss文件(包含在我的SASS主入口点(app.scss)中)在编译SASS之前没有时间生成。

有什么办法告诉webpack等到我的插件完成后再继续构建吗?

要清楚一点,我的所有工作都相对良好,下载了字体,生成了icons.scss等,唯一的问题是SASS在icons.scss存在之前就进行了编译。我不知道哪个编译器/编译钩子最适合使用,所以也希望在此输入一些信息。

请参阅相关部分的代码摘录。我在感觉可以改善的地方留下了评论:

// 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();
            }
        });
});

编辑:我已经研究了文档,但是很难知道没有示例该怎么做。我已经在几乎每个编译器和编译钩子上都设置了回调,以了解它们的运行时间和方式,但这并没有真正帮助我很多。

1 个答案:

答案 0 :(得分:0)

我对这种解决方案不满意,但最终我只通过更改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();