315 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			315 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
 | 
						|
    if (k2 === undefined) k2 = k;
 | 
						|
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
 | 
						|
}) : (function(o, m, k, k2) {
 | 
						|
    if (k2 === undefined) k2 = k;
 | 
						|
    o[k2] = m[k];
 | 
						|
}));
 | 
						|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
 | 
						|
    Object.defineProperty(o, "default", { enumerable: true, value: v });
 | 
						|
}) : function(o, v) {
 | 
						|
    o["default"] = v;
 | 
						|
});
 | 
						|
var __importStar = (this && this.__importStar) || function (mod) {
 | 
						|
    if (mod && mod.__esModule) return mod;
 | 
						|
    var result = {};
 | 
						|
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
 | 
						|
    __setModuleDefault(result, mod);
 | 
						|
    return result;
 | 
						|
};
 | 
						|
Object.defineProperty(exports, "__esModule", { value: true });
 | 
						|
const qs = __importStar(require("querystring"));
 | 
						|
const util_1 = require("./util");
 | 
						|
const resolveScript_1 = require("./resolveScript");
 | 
						|
const compiler_1 = require("./compiler");
 | 
						|
const descriptorCache_1 = require("./descriptorCache");
 | 
						|
const id = 'vue-loader-plugin';
 | 
						|
const NS = 'vue-loader';
 | 
						|
const NormalModule = require('webpack/lib/NormalModule');
 | 
						|
const BasicEffectRulePlugin = require('webpack/lib/rules/BasicEffectRulePlugin');
 | 
						|
const BasicMatcherRulePlugin = require('webpack/lib/rules/BasicMatcherRulePlugin');
 | 
						|
const UseEffectRulePlugin = require('webpack/lib/rules/UseEffectRulePlugin');
 | 
						|
const RuleSetCompiler = require('webpack/lib/rules/RuleSetCompiler');
 | 
						|
let objectMatcherRulePlugins = [];
 | 
						|
try {
 | 
						|
    const ObjectMatcherRulePlugin = require('webpack/lib/rules/ObjectMatcherRulePlugin');
 | 
						|
    objectMatcherRulePlugins.push(new ObjectMatcherRulePlugin('assert', 'assertions'), new ObjectMatcherRulePlugin('descriptionData'));
 | 
						|
}
 | 
						|
catch (e) {
 | 
						|
    const DescriptionDataMatcherRulePlugin = require('webpack/lib/rules/DescriptionDataMatcherRulePlugin');
 | 
						|
    objectMatcherRulePlugins.push(new DescriptionDataMatcherRulePlugin());
 | 
						|
}
 | 
						|
const ruleSetCompiler = new RuleSetCompiler([
 | 
						|
    new BasicMatcherRulePlugin('test', 'resource'),
 | 
						|
    new BasicMatcherRulePlugin('mimetype'),
 | 
						|
    new BasicMatcherRulePlugin('dependency'),
 | 
						|
    new BasicMatcherRulePlugin('include', 'resource'),
 | 
						|
    new BasicMatcherRulePlugin('exclude', 'resource', true),
 | 
						|
    new BasicMatcherRulePlugin('conditions'),
 | 
						|
    new BasicMatcherRulePlugin('resource'),
 | 
						|
    new BasicMatcherRulePlugin('resourceQuery'),
 | 
						|
    new BasicMatcherRulePlugin('resourceFragment'),
 | 
						|
    new BasicMatcherRulePlugin('realResource'),
 | 
						|
    new BasicMatcherRulePlugin('issuer'),
 | 
						|
    new BasicMatcherRulePlugin('compiler'),
 | 
						|
    new BasicMatcherRulePlugin('issuerLayer'),
 | 
						|
    ...objectMatcherRulePlugins,
 | 
						|
    new BasicEffectRulePlugin('type'),
 | 
						|
    new BasicEffectRulePlugin('sideEffects'),
 | 
						|
    new BasicEffectRulePlugin('parser'),
 | 
						|
    new BasicEffectRulePlugin('resolve'),
 | 
						|
    new BasicEffectRulePlugin('generator'),
 | 
						|
    new BasicEffectRulePlugin('layer'),
 | 
						|
    new UseEffectRulePlugin(),
 | 
						|
]);
 | 
						|
class VueLoaderPlugin {
 | 
						|
    apply(compiler) {
 | 
						|
        // @ts-ignore
 | 
						|
        const normalModule = compiler.webpack.NormalModule || NormalModule;
 | 
						|
        // add NS marker so that the loader can detect and report missing plugin
 | 
						|
        compiler.hooks.compilation.tap(id, (compilation) => {
 | 
						|
            normalModule
 | 
						|
                .getCompilationHooks(compilation)
 | 
						|
                .loader.tap(id, (loaderContext) => {
 | 
						|
                loaderContext[NS] = true;
 | 
						|
            });
 | 
						|
        });
 | 
						|
        const rules = compiler.options.module.rules;
 | 
						|
        let rawVueRule;
 | 
						|
        let vueRules = [];
 | 
						|
        for (const rawRule of rules) {
 | 
						|
            // skip rules with 'enforce'. eg. rule for eslint-loader
 | 
						|
            if (rawRule.enforce) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            vueRules = match(rawRule, 'foo.vue');
 | 
						|
            if (!vueRules.length) {
 | 
						|
                vueRules = match(rawRule, 'foo.vue.html');
 | 
						|
            }
 | 
						|
            if (vueRules.length > 0) {
 | 
						|
                if (rawRule.oneOf) {
 | 
						|
                    throw new Error(`[VueLoaderPlugin Error] vue-loader currently does not support vue rules with oneOf.`);
 | 
						|
                }
 | 
						|
                rawVueRule = rawRule;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if (!vueRules.length) {
 | 
						|
            throw new Error(`[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
 | 
						|
                `Make sure there is at least one root-level rule that matches .vue or .vue.html files.`);
 | 
						|
        }
 | 
						|
        // get the normalized "use" for vue files
 | 
						|
        const vueUse = vueRules
 | 
						|
            .filter((rule) => rule.type === 'use')
 | 
						|
            .map((rule) => rule.value);
 | 
						|
        // get vue-loader options
 | 
						|
        const vueLoaderUseIndex = vueUse.findIndex((u) => {
 | 
						|
            // FIXME: this code logic is incorrect when project paths starts with `vue-loader-something`
 | 
						|
            return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader);
 | 
						|
        });
 | 
						|
        if (vueLoaderUseIndex < 0) {
 | 
						|
            throw new Error(`[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` +
 | 
						|
                `Make sure the rule matching .vue files include vue-loader in its use.`);
 | 
						|
        }
 | 
						|
        // make sure vue-loader options has a known ident so that we can share
 | 
						|
        // options by reference in the template-loader by using a ref query like
 | 
						|
        // template-loader??vue-loader-options
 | 
						|
        const vueLoaderUse = vueUse[vueLoaderUseIndex];
 | 
						|
        const vueLoaderOptions = (vueLoaderUse.options =
 | 
						|
            vueLoaderUse.options || {});
 | 
						|
        const enableInlineMatchResource = vueLoaderOptions.experimentalInlineMatchResource;
 | 
						|
        // for each user rule (except the vue rule), create a cloned rule
 | 
						|
        // that targets the corresponding language blocks in *.vue files.
 | 
						|
        const refs = new Map();
 | 
						|
        const clonedRules = rules
 | 
						|
            .filter((r) => r !== rawVueRule)
 | 
						|
            .map((rawRule) => cloneRule(rawRule, refs, langBlockRuleCheck, langBlockRuleResource));
 | 
						|
        // fix conflict with config.loader and config.options when using config.use
 | 
						|
        delete rawVueRule.loader;
 | 
						|
        delete rawVueRule.options;
 | 
						|
        rawVueRule.use = vueUse;
 | 
						|
        // rule for template compiler
 | 
						|
        const templateCompilerRule = {
 | 
						|
            loader: require.resolve('./templateLoader'),
 | 
						|
            resourceQuery: (query) => {
 | 
						|
                if (!query) {
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
                const parsed = qs.parse(query.slice(1));
 | 
						|
                return parsed.vue != null && parsed.type === 'template';
 | 
						|
            },
 | 
						|
            options: vueLoaderOptions,
 | 
						|
        };
 | 
						|
        // for each rule that matches plain .js files, also create a clone and
 | 
						|
        // match it against the compiled template code inside *.vue files, so that
 | 
						|
        // compiled vue render functions receive the same treatment as user code
 | 
						|
        // (mostly babel)
 | 
						|
        const jsRulesForRenderFn = rules
 | 
						|
            .filter((r) => r !== rawVueRule &&
 | 
						|
            (match(r, 'test.js').length > 0 || match(r, 'test.ts').length > 0))
 | 
						|
            .map((rawRule) => cloneRule(rawRule, refs, jsRuleCheck, jsRuleResource));
 | 
						|
        // global pitcher (responsible for injecting template compiler loader & CSS
 | 
						|
        // post loader)
 | 
						|
        const pitcher = {
 | 
						|
            loader: require.resolve('./pitcher'),
 | 
						|
            resourceQuery: (query) => {
 | 
						|
                if (!query) {
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
                const parsed = qs.parse(query.slice(1));
 | 
						|
                return parsed.vue != null;
 | 
						|
            },
 | 
						|
            options: vueLoaderOptions,
 | 
						|
        };
 | 
						|
        // replace original rules
 | 
						|
        if (enableInlineMatchResource) {
 | 
						|
            // Match rules using `vue-loader`
 | 
						|
            const vueLoaderRules = rules.filter((rule) => {
 | 
						|
                const matchOnce = (use) => {
 | 
						|
                    let loaderString = '';
 | 
						|
                    if (!use) {
 | 
						|
                        return loaderString;
 | 
						|
                    }
 | 
						|
                    if (typeof use === 'string') {
 | 
						|
                        loaderString = use;
 | 
						|
                    }
 | 
						|
                    else if (Array.isArray(use)) {
 | 
						|
                        loaderString = matchOnce(use[0]);
 | 
						|
                    }
 | 
						|
                    else if (typeof use === 'object' && use.loader) {
 | 
						|
                        loaderString = use.loader;
 | 
						|
                    }
 | 
						|
                    return loaderString;
 | 
						|
                };
 | 
						|
                const loader = rule.loader || matchOnce(rule.use);
 | 
						|
                return (loader === require('../package.json').name ||
 | 
						|
                    loader.startsWith(require.resolve('./index')));
 | 
						|
            });
 | 
						|
            compiler.options.module.rules = [
 | 
						|
                pitcher,
 | 
						|
                ...rules.filter((rule) => !vueLoaderRules.includes(rule)),
 | 
						|
                templateCompilerRule,
 | 
						|
                ...clonedRules,
 | 
						|
                ...vueLoaderRules,
 | 
						|
            ];
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            compiler.options.module.rules = [
 | 
						|
                pitcher,
 | 
						|
                ...jsRulesForRenderFn,
 | 
						|
                templateCompilerRule,
 | 
						|
                ...clonedRules,
 | 
						|
                ...rules,
 | 
						|
            ];
 | 
						|
        }
 | 
						|
        // 3.3 HMR support for imported types
 | 
						|
        if ((0, util_1.needHMR)(vueLoaderOptions, compiler.options) &&
 | 
						|
            compiler_1.compiler.invalidateTypeCache) {
 | 
						|
            compiler.hooks.afterCompile.tap(id, (compilation) => {
 | 
						|
                if (compilation.compiler === compiler) {
 | 
						|
                    for (const file of resolveScript_1.typeDepToSFCMap.keys()) {
 | 
						|
                        compilation.fileDependencies.add(file);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            });
 | 
						|
            compiler.hooks.watchRun.tap(id, () => {
 | 
						|
                if (!compiler.modifiedFiles)
 | 
						|
                    return;
 | 
						|
                for (const file of compiler.modifiedFiles) {
 | 
						|
                    compiler_1.compiler.invalidateTypeCache(file);
 | 
						|
                    const affectedSFCs = resolveScript_1.typeDepToSFCMap.get(file);
 | 
						|
                    if (affectedSFCs) {
 | 
						|
                        for (const sfc of affectedSFCs) {
 | 
						|
                            // bust script resolve cache
 | 
						|
                            const desc = descriptorCache_1.descriptorCache.get(sfc);
 | 
						|
                            if (desc)
 | 
						|
                                resolveScript_1.clientCache.delete(desc);
 | 
						|
                            // force update importing SFC
 | 
						|
                            // @ts-ignore
 | 
						|
                            compiler.fileTimestamps.set(sfc, {
 | 
						|
                                safeTime: Date.now(),
 | 
						|
                                timestamp: Date.now(),
 | 
						|
                            });
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                for (const file of compiler.removedFiles) {
 | 
						|
                    compiler_1.compiler.invalidateTypeCache(file);
 | 
						|
                }
 | 
						|
            });
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
VueLoaderPlugin.NS = NS;
 | 
						|
const matcherCache = new WeakMap();
 | 
						|
function match(rule, fakeFile) {
 | 
						|
    let ruleSet = matcherCache.get(rule);
 | 
						|
    if (!ruleSet) {
 | 
						|
        // skip the `include` check when locating the vue rule
 | 
						|
        const clonedRawRule = Object.assign({}, rule);
 | 
						|
        delete clonedRawRule.include;
 | 
						|
        ruleSet = ruleSetCompiler.compile([clonedRawRule]);
 | 
						|
        matcherCache.set(rule, ruleSet);
 | 
						|
    }
 | 
						|
    return ruleSet.exec({
 | 
						|
        resource: fakeFile,
 | 
						|
    });
 | 
						|
}
 | 
						|
const langBlockRuleCheck = (query, rule) => {
 | 
						|
    return (query.type === 'custom' || !rule.conditions.length || query.lang != null);
 | 
						|
};
 | 
						|
const langBlockRuleResource = (query, resource) => `${resource}.${query.lang}`;
 | 
						|
const jsRuleCheck = (query) => {
 | 
						|
    return query.type === 'template';
 | 
						|
};
 | 
						|
const jsRuleResource = (query, resource) => `${resource}.${query.ts ? `ts` : `js`}`;
 | 
						|
let uid = 0;
 | 
						|
function cloneRule(rawRule, refs, ruleCheck, ruleResource) {
 | 
						|
    const compiledRule = ruleSetCompiler.compileRule(`clonedRuleSet-${++uid}`, rawRule, refs);
 | 
						|
    // do not process rule with enforce
 | 
						|
    if (!rawRule.enforce) {
 | 
						|
        const ruleUse = compiledRule.effects
 | 
						|
            .filter((effect) => effect.type === 'use')
 | 
						|
            .map((effect) => effect.value);
 | 
						|
        // fix conflict with config.loader and config.options when using config.use
 | 
						|
        delete rawRule.loader;
 | 
						|
        delete rawRule.options;
 | 
						|
        rawRule.use = ruleUse;
 | 
						|
    }
 | 
						|
    let currentResource;
 | 
						|
    const res = Object.assign(Object.assign({}, rawRule), { resource: (resources) => {
 | 
						|
            currentResource = resources;
 | 
						|
            return true;
 | 
						|
        }, resourceQuery: (query) => {
 | 
						|
            if (!query) {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            const parsed = qs.parse(query.slice(1));
 | 
						|
            if (parsed.vue == null) {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            if (!ruleCheck(parsed, compiledRule)) {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            const fakeResourcePath = ruleResource(parsed, currentResource);
 | 
						|
            for (const condition of compiledRule.conditions) {
 | 
						|
                // add support for resourceQuery
 | 
						|
                const request = condition.property === 'resourceQuery' ? query : fakeResourcePath;
 | 
						|
                if (condition && !condition.fn(request)) {
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            return true;
 | 
						|
        } });
 | 
						|
    delete res.test;
 | 
						|
    if (rawRule.rules) {
 | 
						|
        res.rules = rawRule.rules.map((rule) => cloneRule(rule, refs, ruleCheck, ruleResource));
 | 
						|
    }
 | 
						|
    if (rawRule.oneOf) {
 | 
						|
        res.oneOf = rawRule.oneOf.map((rule) => cloneRule(rule, refs, ruleCheck, ruleResource));
 | 
						|
    }
 | 
						|
    return res;
 | 
						|
}
 | 
						|
exports.default = VueLoaderPlugin;
 |