提交 306514b3 编写于 作者: fxy060608's avatar fxy060608

wip(app): nvue

上级 6a75f845
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.babelLoader = void 0;
exports.babelLoader = {
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
babelrc: false,
},
},
],
};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.vueLoader = void 0;
exports.vueLoader = {
test: [/\.nvue(\?[^?]+)?$/, /\.vue(\?[^?]+)?$/],
use: [
{
loader: 'vue-loader',
options: {
compiler: require('../../../../../lib/weex-template-compiler'),
},
},
],
};
......@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.optimization = void 0;
const terser_webpack_plugin_1 = __importDefault(require("terser-webpack-plugin"));
exports.optimization = {
nodeEnv: false,
minimizer: [
new terser_webpack_plugin_1.default({
terserOptions: {
......
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const StyleInineLoader = function (source) {
// TODO minify this?
return `export default ${JSON.stringify(source)}`;
};
exports.default = StyleInineLoader;
function pageLoader(content) { }
exports.default = pageLoader;
export declare function genCSSModulesCode(id: string, index: number, request: string, moduleName: string | boolean, needsHotReload: boolean): string;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.genCSSModulesCode = void 0;
function genCSSModulesCode(id, index, request, moduleName, needsHotReload) {
const styleVar = `style${index}`;
let code = `\nimport ${styleVar} from ${request}`;
// inject variable
const name = typeof moduleName === 'string' ? moduleName : '$style';
code += `\ncssModules["${name}"] = ${styleVar}`;
if (needsHotReload) {
code += `
if (module.hot) {
module.hot.accept(${request}, () => {
cssModules["${name}"] = ${styleVar}
__VUE_HMR_RUNTIME__.rerender("${id}")
})
}`;
}
return code;
}
exports.genCSSModulesCode = genCSSModulesCode;
import { SFCDescriptor } from '@vue/compiler-sfc';
export declare function setDescriptor(filename: string, entry: SFCDescriptor): void;
export declare function getDescriptor(filename: string): SFCDescriptor;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDescriptor = exports.setDescriptor = void 0;
const fs = require("fs");
const compiler_sfc_1 = require("@vue/compiler-sfc");
const cache = new Map();
function setDescriptor(filename, entry) {
cache.set(cleanQuery(filename), entry);
}
exports.setDescriptor = setDescriptor;
function getDescriptor(filename) {
filename = cleanQuery(filename);
if (cache.has(filename)) {
return cache.get(filename);
}
// This function should only be called after the descriptor has been
// cached by the main loader.
// If this is somehow called without a cache hit, it's probably due to sub
// loaders being run in separate threads. The only way to deal with this is to
// read from disk directly...
const source = fs.readFileSync(filename, 'utf-8');
const { descriptor } = compiler_sfc_1.parse(source, {
filename,
sourceMap: true,
});
cache.set(filename, descriptor);
return descriptor;
}
exports.getDescriptor = getDescriptor;
function cleanQuery(str) {
const i = str.indexOf('?');
return i > 0 ? str.slice(0, i) : str;
}
import { CompilerError } from '@vue/compiler-sfc';
export declare function formatError(err: SyntaxError | CompilerError, source: string, file: string): void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatError = void 0;
const compiler_sfc_1 = require("@vue/compiler-sfc");
const chalk = require("chalk");
function formatError(err, source, file) {
const loc = err.loc;
if (!loc) {
return;
}
const locString = `:${loc.start.line}:${loc.start.column}`;
const filePath = chalk.gray(`at ${file}${locString}`);
const codeframe = compiler_sfc_1.generateCodeFrame(source, loc.start.offset, loc.end.offset);
err.message = `\n${chalk.red(`VueCompilerError: ${err.message}`)}\n${filePath}\n${chalk.yellow(codeframe)}\n`;
}
exports.formatError = formatError;
export declare function genHotReloadCode(id: string, templateRequest: string | undefined): string;
"use strict";
// __VUE_HMR_RUNTIME__ is injected to global scope by @vue/runtime-core
Object.defineProperty(exports, "__esModule", { value: true });
exports.genHotReloadCode = void 0;
function genHotReloadCode(id, templateRequest) {
return `
/* hot reload */
if (module.hot) {
script.__hmrId = "${id}"
const api = __VUE_HMR_RUNTIME__
module.hot.accept()
if (!api.createRecord('${id}', script)) {
api.reload('${id}', script)
}
${templateRequest ? genTemplateHotReloadCode(id, templateRequest) : ''}
}
`;
}
exports.genHotReloadCode = genHotReloadCode;
function genTemplateHotReloadCode(id, request) {
return `
module.hot.accept(${request}, () => {
api.rerender('${id}', render)
})
`;
}
import webpack = require('webpack');
import { TemplateCompiler, CompilerOptions, SFCTemplateCompileOptions, SFCScriptCompileOptions } from '@vue/compiler-sfc';
import VueLoaderPlugin from './plugin';
export { VueLoaderPlugin };
export interface VueLoaderOptions {
babelParserPlugins?: SFCScriptCompileOptions['babelParserPlugins'];
transformAssetUrls?: SFCTemplateCompileOptions['transformAssetUrls'];
compiler?: TemplateCompiler | string;
compilerOptions?: CompilerOptions;
refSugar?: boolean;
customElement?: boolean | RegExp;
hotReload?: boolean;
exposeFilename?: boolean;
appendExtension?: boolean;
isServerBuild?: boolean;
}
export default function loader(this: webpack.loader.LoaderContext, source: string): string | void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VueLoaderPlugin = void 0;
try {
require.resolve('@vue/compiler-sfc');
}
catch (e) {
throw new Error('vue-loader requires @vue/compiler-sfc to be present in the dependency ' +
'tree.');
}
const path = require("path");
const qs = require("querystring");
const loaderUtils = require("loader-utils");
const hash = require("hash-sum");
const compiler_sfc_1 = require("@vue/compiler-sfc");
const select_1 = require("./select");
const hotReload_1 = require("./hotReload");
const cssModules_1 = require("./cssModules");
const formatError_1 = require("./formatError");
const plugin_1 = require("./plugin");
exports.VueLoaderPlugin = plugin_1.default;
const resolveScript_1 = require("./resolveScript");
const descriptorCache_1 = require("./descriptorCache");
let errorEmitted = false;
function loader(source) {
var _a;
const loaderContext = this;
// check if plugin is installed
if (!errorEmitted &&
!loaderContext['thread-loader'] &&
!loaderContext[plugin_1.default.NS]) {
loaderContext.emitError(new Error(`vue-loader was used without the corresponding plugin. ` +
`Make sure to include VueLoaderPlugin in your webpack config.`));
errorEmitted = true;
}
const stringifyRequest = (r) => loaderUtils.stringifyRequest(loaderContext, r);
const { mode, target, sourceMap, rootContext, resourcePath, resourceQuery = '', } = loaderContext;
const rawQuery = resourceQuery.slice(1);
const incomingQuery = qs.parse(rawQuery);
const options = (loaderUtils.getOptions(loaderContext) ||
{});
const isServer = (_a = options.isServerBuild) !== null && _a !== void 0 ? _a : target === 'node';
const isProduction = mode === 'production' || process.env.NODE_ENV === 'production';
const filename = resourcePath.replace(/\?.*$/, '');
const { descriptor, errors } = compiler_sfc_1.parse(source, {
filename,
sourceMap,
});
const asCustomElement = typeof options.customElement === 'boolean'
? options.customElement
: (options.customElement || /\.ce\.vue$/).test(filename);
// cache descriptor
descriptorCache_1.setDescriptor(filename, descriptor);
if (errors.length) {
errors.forEach((err) => {
formatError_1.formatError(err, source, resourcePath);
loaderContext.emitError(err);
});
return ``;
}
// module id for scoped CSS & hot-reload
const rawShortFilePath = path
.relative(rootContext || process.cwd(), filename)
.replace(/^(\.\.[\/\\])+/, '');
const shortFilePath = rawShortFilePath.replace(/\\/g, '/');
const id = hash(isProduction
? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
: shortFilePath);
// if the query has a type field, this is a language block request
// e.g. foo.vue?type=template&id=xxxxx
// and we will return early
if (incomingQuery.type) {
return select_1.selectBlock(descriptor, id, options, loaderContext, incomingQuery, !!options.appendExtension);
}
// feature information
const hasScoped = descriptor.styles.some((s) => s.scoped);
const needsHotReload = !isServer &&
!isProduction &&
!!(descriptor.script || descriptor.scriptSetup || descriptor.template) &&
options.hotReload !== false;
// script
let scriptImport = `const script = {}`;
const { script, scriptSetup } = descriptor;
if (script || scriptSetup) {
const src = (script && !scriptSetup && script.src) || resourcePath;
const attrsQuery = attrsToQuery((scriptSetup || script).attrs, 'js');
const query = `?vue&type=script${attrsQuery}${resourceQuery}`;
const scriptRequest = stringifyRequest(src + query);
scriptImport =
`import script from ${scriptRequest}\n` +
// support named exports
`export * from ${scriptRequest}`;
}
// template
let templateImport = ``;
let templateRequest;
const renderFnName = isServer ? `ssrRender` : `render`;
const useInlineTemplate = resolveScript_1.canInlineTemplate(descriptor, isProduction);
if (descriptor.template && !useInlineTemplate) {
const src = descriptor.template.src || resourcePath;
const idQuery = `&id=${id}`;
const scopedQuery = hasScoped ? `&scoped=true` : ``;
const attrsQuery = attrsToQuery(descriptor.template.attrs);
// const bindingsQuery = script
// ? `&bindings=${JSON.stringify(script.bindings ?? {})}`
// : ``
// const varsQuery = descriptor.cssVars
// ? `&vars=${qs.escape(generateCssVars(descriptor, id, isProduction))}`
// : ``
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${resourceQuery}`;
templateRequest = stringifyRequest(src + query);
templateImport = `import { ${renderFnName} } from ${templateRequest}`;
}
// styles
let stylesCode = ``;
let hasCSSModules = false;
const nonWhitespaceRE = /\S+/;
if (descriptor.styles.length) {
descriptor.styles
.filter((style) => style.src || nonWhitespaceRE.test(style.content))
.forEach((style, i) => {
const src = style.src || resourcePath;
const attrsQuery = attrsToQuery(style.attrs, 'css');
// make sure to only pass id when necessary so that we don't inject
// duplicate tags when multiple components import the same css file
const idQuery = !style.src || style.scoped ? `&id=${id}` : ``;
const inlineQuery = asCustomElement ? `&inline` : ``;
const query = `?vue&type=style&index=${i}${idQuery}${inlineQuery}${attrsQuery}${resourceQuery}`;
const styleRequest = stringifyRequest(src + query);
if (style.module) {
if (asCustomElement) {
loaderContext.emitError(`<style module> is not supported in custom element mode.`);
}
if (!hasCSSModules) {
stylesCode += `\nconst cssModules = script.__cssModules = {}`;
hasCSSModules = true;
}
stylesCode += cssModules_1.genCSSModulesCode(id, i, styleRequest, style.module, needsHotReload);
}
else {
if (asCustomElement) {
stylesCode += `\nimport _style_${i} from ${styleRequest}`;
}
else {
stylesCode += `\nimport ${styleRequest}`;
}
}
// TODO SSR critical CSS collection
});
if (asCustomElement) {
stylesCode += `\nscript.styles = [${descriptor.styles.map((_, i) => `_style_${i}`)}]`;
}
}
let code = [
templateImport,
scriptImport,
stylesCode,
templateImport ? `script.${renderFnName} = ${renderFnName}` : ``,
]
.filter(Boolean)
.join('\n');
// attach scope Id for runtime use
if (hasScoped) {
code += `\nscript.__scopeId = "data-v-${id}"`;
}
if (needsHotReload) {
code += hotReload_1.genHotReloadCode(id, templateRequest);
}
// Expose filename. This is used by the devtools and Vue runtime warnings.
if (!isProduction) {
// Expose the file's full path in development, so that it can be opened
// from the devtools.
code += `\nscript.__file = ${JSON.stringify(rawShortFilePath.replace(/\\/g, '/'))}`;
}
else if (options.exposeFilename) {
// Libraries can opt-in to expose their components' filenames in production builds.
// For security reasons, only expose the file's basename in production.
code += `\nscript.__file = ${JSON.stringify(path.basename(resourcePath))}`;
}
// custom blocks
if (descriptor.customBlocks && descriptor.customBlocks.length) {
code += `\n/* custom blocks */\n`;
code +=
descriptor.customBlocks
.map((block, i) => {
const src = block.attrs.src || resourcePath;
const attrsQuery = attrsToQuery(block.attrs);
const blockTypeQuery = `&blockType=${qs.escape(block.type)}`;
const issuerQuery = block.attrs.src
? `&issuerPath=${qs.escape(resourcePath)}`
: '';
const query = `?vue&type=custom&index=${i}${blockTypeQuery}${issuerQuery}${attrsQuery}${resourceQuery}`;
return (`import block${i} from ${stringifyRequest(src + query)}\n` +
`if (typeof block${i} === 'function') block${i}(script)`);
})
.join(`\n`) + `\n`;
}
// finalize
code += asCustomElement
? `\n\nimport { defineCustomElement as __ce } from 'vue';` +
`export default __ce(script)`
: `\n\nexport default script`;
return code;
}
exports.default = loader;
// these are built-in query parameters so should be ignored
// if the user happen to add them as attrs
const ignoreList = ['id', 'index', 'src', 'type'];
function attrsToQuery(attrs, langFallback) {
let query = ``;
for (const name in attrs) {
const value = attrs[name];
if (!ignoreList.includes(name)) {
query += `&${qs.escape(name)}=${value ? qs.escape(String(value)) : ``}`;
}
}
if (langFallback && !(`lang` in attrs)) {
query += `&lang=${langFallback}`;
}
return query;
}
import webpack = require('webpack');
declare const pitcher: webpack.loader.Loader;
export declare const pitch: () => string | undefined;
export default pitcher;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.pitch = void 0;
const qs = require("querystring");
const loaderUtils = require("loader-utils");
const selfPath = require.resolve('./index');
// const templateLoaderPath = require.resolve('./templateLoader')
const stylePostLoaderPath = require.resolve('./stylePostLoader');
const styleInlineLoaderPath = require.resolve('./styleInlineLoader');
const isESLintLoader = (l) => /(\/|\\|@)eslint-loader/.test(l.path);
const isNullLoader = (l) => /(\/|\\|@)null-loader/.test(l.path);
const isCSSLoader = (l) => /(\/|\\|@)css-loader/.test(l.path);
const isCacheLoader = (l) => /(\/|\\|@)cache-loader/.test(l.path);
const isNotPitcher = (l) => l.path !== __filename;
const pitcher = (code) => code;
// This pitching loader is responsible for intercepting all vue block requests
// and transform it into appropriate requests.
exports.pitch = function () {
const context = this;
const rawLoaders = context.loaders.filter(isNotPitcher);
let loaders = rawLoaders;
// do not inject if user uses null-loader to void the type (#1239)
if (loaders.some(isNullLoader)) {
return;
}
const query = qs.parse(context.resourceQuery.slice(1));
const isInlineBlock = /\.vue$/.test(context.resourcePath);
// eslint-loader may get matched multiple times
// if this is an inline block, since the whole file itself is being linted,
// remove eslint-loader to avoid duplicate linting.
if (isInlineBlock) {
loaders = loaders.filter((l) => !isESLintLoader(l));
}
// Important: dedupe loaders since both the original rule
// and the cloned rule would match a source import request or a
// resourceQuery-only rule that intends to target a custom block with no lang
const seen = new Map();
loaders = loaders.filter((loader) => {
const identifier = typeof loader === 'string'
? loader
: // Dedupe based on both path and query if available. This is important
// in Vue CLI so that postcss-loaders with different options can co-exist
loader.path + loader.query;
if (!seen.has(identifier)) {
seen.set(identifier, true);
return true;
}
});
// Inject style-post-loader before css-loader for scoped CSS and trimming
if (query.type === `style`) {
const cssLoaderIndex = loaders.findIndex(isCSSLoader);
if (cssLoaderIndex > -1) {
// if inlined, ignore any loaders after css-loader and replace w/ inline
// loader
const afterLoaders = query.inline != null
? [styleInlineLoaderPath]
: loaders.slice(0, cssLoaderIndex + 1);
const beforeLoaders = loaders.slice(cssLoaderIndex + 1);
return genProxyModule([...afterLoaders, stylePostLoaderPath, ...beforeLoaders], context, !!query.module || query.inline != null);
}
}
// if a custom block has no other matching loader other than vue-loader itself
// or cache-loader, we should ignore it
if (query.type === `custom` && shouldIgnoreCustomBlock(loaders)) {
return ``;
}
// Rewrite request. Technically this should only be done when we have deduped
// loaders. But somehow this is required for block source maps to work.
return genProxyModule(loaders, context, query.type !== 'template');
};
function genProxyModule(loaders, context, exportDefault = true) {
const request = genRequest(loaders, context);
// return a proxy module which simply re-exports everything from the
// actual request. Note for template blocks the compiled module has no
// default export.
return ((exportDefault ? `export { default } from ${request}; ` : ``) +
`export * from ${request}`);
}
function genRequest(loaders, context) {
const loaderStrings = loaders.map((loader) => {
return typeof loader === 'string' ? loader : loader.request;
});
const resource = context.resourcePath + context.resourceQuery;
return loaderUtils.stringifyRequest(context, '-!' + [...loaderStrings, resource].join('!'));
}
function shouldIgnoreCustomBlock(loaders) {
const actualLoaders = loaders.filter((loader) => {
// vue-loader
if (loader.path === selfPath) {
return false;
}
// cache-loader
if (isCacheLoader(loader)) {
return false;
}
return true;
});
return actualLoaders.length === 0;
}
exports.default = pitcher;
import webpack = require('webpack');
declare class VueLoaderPlugin implements webpack.Plugin {
static NS: string;
apply(compiler: webpack.Compiler): void;
}
declare let Plugin: typeof VueLoaderPlugin;
export default Plugin;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const webpack = require("webpack");
let Plugin;
if (webpack.version && webpack.version[0] > '4') {
// webpack5 and upper
Plugin = require('./pluginWebpack5').default;
}
else {
// webpack4 and lower
Plugin = require('./pluginWebpack4').default;
}
exports.default = Plugin;
import webpack = require('webpack');
declare class VueLoaderPlugin implements webpack.Plugin {
static NS: string;
apply(compiler: webpack.Compiler): void;
}
export default VueLoaderPlugin;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const qs = require("querystring");
const RuleSet = require('webpack/lib/RuleSet');
const id = 'vue-loader-plugin';
const NS = 'vue-loader';
class VueLoaderPlugin {
apply(compiler) {
// inject NS for plugin installation check in the main loader
compiler.hooks.compilation.tap(id, (compilation) => {
compilation.hooks.normalModuleLoader.tap(id, (loaderContext) => {
loaderContext[NS] = true;
});
});
const rawRules = compiler.options.module.rules;
// use webpack's RuleSet utility to normalize user rules
const rules = new RuleSet(rawRules).rules;
// find the rule that applies to vue files
let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`));
if (vueRuleIndex < 0) {
vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`));
}
const vueRule = rules[vueRuleIndex];
if (!vueRule) {
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.`);
}
if (vueRule.oneOf) {
throw new Error(`[VueLoaderPlugin Error] vue-loader currently does not support vue rules with oneOf.`);
}
// get the normlized "use" for vue files
const vueUse = vueRule.use;
// 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.`);
}
const vueLoaderUse = vueUse[vueLoaderUseIndex];
const vueLoaderOptions = (vueLoaderUse.options =
vueLoaderUse.options || {});
// for each user rule (expect the vue rule), create a cloned rule
// that targets the corresponding language blocks in *.vue files.
const clonedRules = rules.filter((r) => r !== vueRule).map(cloneRule);
// rule for template compiler
const templateCompilerRule = {
loader: require.resolve('./templateLoader'),
resourceQuery: (query) => {
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 matchesJS = createMatcher(`test.js`);
const jsRulesForRenderFn = rules
.filter((r) => r !== vueRule && matchesJS(r))
.map(cloneRuleForRenderFn);
// pitcher for block requests (for injecting stylePostLoader and deduping
// loaders matched for src imports)
const pitcher = {
loader: require.resolve('./pitcher'),
resourceQuery: (query) => {
const parsed = qs.parse(query.slice(1));
return parsed.vue != null;
},
};
// replace original rules
compiler.options.module.rules = [
pitcher,
...jsRulesForRenderFn,
templateCompilerRule,
...clonedRules,
...rules,
];
}
}
VueLoaderPlugin.NS = NS;
function createMatcher(fakeFile) {
return (rule) => {
// #1201 we need to skip the `include` check when locating the vue rule
const clone = Object.assign({}, rule);
delete clone.include;
const normalized = RuleSet.normalizeRule(clone, {}, '');
return !rule.enforce && normalized.resource && normalized.resource(fakeFile);
};
}
function cloneRule(rule) {
const resource = rule.resource;
const resourceQuery = rule.resourceQuery;
// Assuming `test` and `resourceQuery` tests are executed in series and
// synchronously (which is true based on RuleSet's implementation), we can
// save the current resource being matched from `test` so that we can access
// it in `resourceQuery`. This ensures when we use the normalized rule's
// resource check, include/exclude are matched correctly.
let currentResource;
const res = Object.assign(Object.assign({}, rule), { resource: (resource) => {
currentResource = resource;
return true;
}, resourceQuery: (query) => {
const parsed = qs.parse(query.slice(1));
if (parsed.vue == null) {
return false;
}
if (resource && parsed.lang == null) {
return false;
}
const fakeResourcePath = `${currentResource}.${parsed.lang}`;
if (resource && !resource(fakeResourcePath)) {
return false;
}
if (resourceQuery && !resourceQuery(query)) {
return false;
}
return true;
} });
if (rule.rules) {
res.rules = rule.rules.map(cloneRule);
}
if (rule.oneOf) {
res.oneOf = rule.oneOf.map(cloneRule);
}
return res;
}
function cloneRuleForRenderFn(rule) {
const resource = rule.resource;
const resourceQuery = rule.resourceQuery;
let currentResource;
const res = Object.assign(Object.assign({}, rule), { resource: (resource) => {
currentResource = resource;
return true;
}, resourceQuery: (query) => {
const parsed = qs.parse(query.slice(1));
if (parsed.vue == null || parsed.type !== 'template') {
return false;
}
const fakeResourcePath = `${currentResource}.js`;
if (resource && !resource(fakeResourcePath)) {
return false;
}
if (resourceQuery && !resourceQuery(query)) {
return false;
}
return true;
} });
if (rule.rules) {
res.rules = rule.rules.map(cloneRuleForRenderFn);
}
if (rule.oneOf) {
res.oneOf = rule.oneOf.map(cloneRuleForRenderFn);
}
return res;
}
exports.default = VueLoaderPlugin;
import { Compiler, Plugin } from 'webpack';
declare class VueLoaderPlugin implements Plugin {
static NS: string;
apply(compiler: Compiler): void;
}
export default VueLoaderPlugin;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const qs = require("querystring");
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 DescriptionDataMatcherRulePlugin = require('webpack/lib/rules/DescriptionDataMatcherRulePlugin');
const UseEffectRulePlugin = require('webpack/lib/rules/UseEffectRulePlugin');
const RuleSetCompiler = require('webpack/lib/rules/RuleSetCompiler');
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 DescriptionDataMatcherRulePlugin(),
new BasicEffectRulePlugin('type'),
new BasicEffectRulePlugin('sideEffects'),
new BasicEffectRulePlugin('parser'),
new BasicEffectRulePlugin('resolve'),
new BasicEffectRulePlugin('generator'),
new UseEffectRulePlugin(),
]);
class VueLoaderPlugin {
apply(compiler) {
// 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 normlized "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 || {});
// for each user rule (expect 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)
.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;
},
};
// replace original rules
compiler.options.module.rules = [
pitcher,
...jsRulesForRenderFn,
templateCompilerRule,
...clonedRules,
...rules,
];
}
}
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}.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;
import webpack = require('webpack');
import { SFCDescriptor, SFCScriptBlock } from '@vue/compiler-sfc';
import { VueLoaderOptions } from 'src';
/**
* inline template mode can only be enabled if:
* - is production (separate compilation needed for HMR during dev)
* - template has no pre-processor (separate loader chain required)
* - template is not using src
*/
export declare function canInlineTemplate(descriptor: SFCDescriptor, isProd: boolean): boolean;
export declare function resolveScript(descriptor: SFCDescriptor, scopeId: string, options: VueLoaderOptions, loaderContext: webpack.loader.LoaderContext): SFCScriptBlock | null;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveScript = exports.canInlineTemplate = void 0;
const compiler_sfc_1 = require("@vue/compiler-sfc");
const clientCache = new WeakMap();
const serverCache = new WeakMap();
/**
* inline template mode can only be enabled if:
* - is production (separate compilation needed for HMR during dev)
* - template has no pre-processor (separate loader chain required)
* - template is not using src
*/
function canInlineTemplate(descriptor, isProd) {
const templateLang = descriptor.template && descriptor.template.lang;
const templateSrc = descriptor.template && descriptor.template.src;
return isProd && !!descriptor.scriptSetup && !templateLang && !templateSrc;
}
exports.canInlineTemplate = canInlineTemplate;
function resolveScript(descriptor, scopeId, options, loaderContext) {
var _a;
if (!descriptor.script && !descriptor.scriptSetup) {
return null;
}
const isProd = loaderContext.mode === 'production' || process.env.NODE_ENV === 'production';
const isServer = (_a = options.isServerBuild) !== null && _a !== void 0 ? _a : loaderContext.target === 'node';
const enableInline = canInlineTemplate(descriptor, isProd);
const cacheToUse = isServer ? serverCache : clientCache;
const cached = cacheToUse.get(descriptor);
if (cached) {
return cached;
}
let resolved = null;
let compiler;
if (typeof options.compiler === 'string') {
compiler = require(options.compiler);
}
else {
compiler = options.compiler;
}
if (compiler_sfc_1.compileScript) {
try {
resolved = compiler_sfc_1.compileScript(descriptor, {
id: scopeId,
isProd,
inlineTemplate: enableInline,
refSugar: options.refSugar,
babelParserPlugins: options.babelParserPlugins,
templateOptions: {
ssr: isServer,
compiler,
compilerOptions: options.compilerOptions,
transformAssetUrls: options.transformAssetUrls || true,
},
});
}
catch (e) {
loaderContext.emitError(e);
}
}
else if (descriptor.scriptSetup) {
loaderContext.emitError(`<script setup> is not supported by the installed version of ` +
`@vue/compiler-sfc - please upgrade.`);
}
else {
resolved = descriptor.script;
}
cacheToUse.set(descriptor, resolved);
return resolved;
}
exports.resolveScript = resolveScript;
/// <reference types="node" />
import webpack = require('webpack');
import { SFCDescriptor } from '@vue/compiler-sfc';
import { ParsedUrlQuery } from 'querystring';
import { VueLoaderOptions } from 'src';
export declare function selectBlock(descriptor: SFCDescriptor, scopeId: string, options: VueLoaderOptions, loaderContext: webpack.loader.LoaderContext, query: ParsedUrlQuery, appendExtension: boolean): void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.selectBlock = void 0;
const resolveScript_1 = require("./resolveScript");
function selectBlock(descriptor, scopeId, options, loaderContext, query, appendExtension) {
// template
if (query.type === `template`) {
// if we are receiving a query with type it can only come from a *.vue file
// that contains that block, so the block is guaranteed to exist.
const template = descriptor.template;
if (appendExtension) {
loaderContext.resourcePath += '.' + (template.lang || 'html');
}
loaderContext.callback(null, template.content, template.map);
return;
}
// script
if (query.type === `script`) {
const script = resolveScript_1.resolveScript(descriptor, scopeId, options, loaderContext);
if (appendExtension) {
loaderContext.resourcePath += '.' + (script.lang || 'js');
}
loaderContext.callback(null, script.content, script.map);
return;
}
// styles
if (query.type === `style` && query.index != null) {
const style = descriptor.styles[Number(query.index)];
if (appendExtension) {
loaderContext.resourcePath += '.' + (style.lang || 'css');
}
loaderContext.callback(null, style.content, style.map);
return;
}
// custom
if (query.type === 'custom' && query.index != null) {
const block = descriptor.customBlocks[Number(query.index)];
loaderContext.callback(null, block.content, block.map);
}
}
exports.selectBlock = selectBlock;
import webpack = require('webpack');
declare const StyleInineLoader: webpack.loader.Loader;
export default StyleInineLoader;
import webpack = require('webpack');
declare const StylePostLoader: webpack.loader.Loader;
export default StylePostLoader;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const qs = require("querystring");
const compiler_sfc_1 = require("@vue/compiler-sfc");
// This is a post loader that handles scoped CSS transforms.
// Injected right before css-loader by the global pitcher (../pitch.js)
// for any <style scoped> selection requests initiated from within vue files.
const StylePostLoader = function (source, inMap) {
const query = qs.parse(this.resourceQuery.slice(1));
const { code, map, errors } = compiler_sfc_1.compileStyle({
source: source,
filename: this.resourcePath,
id: `data-v-${query.id}`,
map: inMap,
scoped: !!query.scoped,
trim: true,
isProd: this.mode === 'production' || process.env.NODE_ENV === 'production',
});
if (errors.length) {
this.callback(errors[0]);
}
else {
this.callback(null, code, map);
}
};
exports.default = StylePostLoader;
import webpack = require('webpack');
declare const TemplateLoader: webpack.loader.Loader;
export default TemplateLoader;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const qs = require("querystring");
const loaderUtils = require("loader-utils");
const formatError_1 = require("./formatError");
const compiler_sfc_1 = require("@vue/compiler-sfc");
const descriptorCache_1 = require("./descriptorCache");
const resolveScript_1 = require("./resolveScript");
// Loader that compiles raw template into JavaScript functions.
// This is injected by the global pitcher (../pitch) for template
// selection requests initiated from vue files.
const TemplateLoader = function (source, inMap) {
var _a;
source = String(source);
const loaderContext = this;
// although this is not the main vue-loader, we can get access to the same
// vue-loader options because we've set an ident in the plugin and used that
// ident to create the request for this loader in the pitcher.
const options = (loaderUtils.getOptions(loaderContext) ||
{});
const isServer = (_a = options.isServerBuild) !== null && _a !== void 0 ? _a : loaderContext.target === 'node';
const isProd = loaderContext.mode === 'production' || process.env.NODE_ENV === 'production';
const query = qs.parse(loaderContext.resourceQuery.slice(1));
const scopeId = query.id;
const descriptor = descriptorCache_1.getDescriptor(loaderContext.resourcePath);
const script = resolveScript_1.resolveScript(descriptor, query.id, options, loaderContext);
let compiler;
if (typeof options.compiler === 'string') {
compiler = require(options.compiler);
}
else {
compiler = options.compiler;
}
const compiled = compiler_sfc_1.compileTemplate({
source,
filename: loaderContext.resourcePath,
inMap,
id: scopeId,
scoped: !!query.scoped,
slotted: descriptor.slotted,
isProd,
ssr: isServer,
ssrCssVars: descriptor.cssVars,
compiler,
compilerOptions: Object.assign(Object.assign({}, options.compilerOptions), { scopeId: query.scoped ? `data-v-${scopeId}` : undefined, bindingMetadata: script ? script.bindings : undefined }),
transformAssetUrls: options.transformAssetUrls || true,
});
// tips
if (compiled.tips.length) {
compiled.tips.forEach((tip) => {
loaderContext.emitWarning(tip);
});
}
// errors
if (compiled.errors && compiled.errors.length) {
compiled.errors.forEach((err) => {
if (typeof err === 'string') {
loaderContext.emitError(err);
}
else {
formatError_1.formatError(err, inMap ? inMap.sourcesContent[0] : source, loaderContext.resourcePath);
loaderContext.emitError(err);
}
});
}
const { code, map } = compiled;
loaderContext.callback(null, code, map);
};
exports.default = TemplateLoader;
const qs = require('querystring')
const { attrsToQuery } = require('./utils')
module.exports = function genCustomBlocksCode (
blocks,
resourcePath,
resourceQuery,
stringifyRequest
) {
return `\n/* custom blocks */\n` + blocks.map((block, i) => {
const src = block.attrs.src || resourcePath
const attrsQuery = attrsToQuery(block.attrs)
const issuerQuery = block.attrs.src ? `&issuerPath=${qs.escape(resourcePath)}` : ''
const inheritQuery = resourceQuery ? `&${resourceQuery.slice(1)}` : ''
const query = `?vue&type=custom&index=${i}&blockType=${qs.escape(block.type)}${issuerQuery}${attrsQuery}${inheritQuery}`
return (
`import block${i} from ${stringifyRequest(src + query)}\n` +
`if (typeof block${i} === 'function') block${i}(component)`
)
}).join(`\n`) + `\n`
}
const hotReloadAPIPath = JSON.stringify(require.resolve('vue-hot-reload-api'))
const genTemplateHotReloadCode = (id, request) => {
return `
module.hot.accept(${request}, function () {
api.rerender('${id}', {
render: render,
staticRenderFns: staticRenderFns
})
})
`.trim()
}
exports.genHotReloadCode = (id, functional, templateRequest) => {
return `
/* hot reload */
if (module.hot) {
var api = require(${hotReloadAPIPath})
api.install(require('vue'))
if (api.compatible) {
module.hot.accept()
if (!api.isRecorded('${id}')) {
api.createRecord('${id}', component.options)
} else {
api.${functional ? 'rerender' : 'reload'}('${id}', component.options)
}
${templateRequest ? genTemplateHotReloadCode(id, templateRequest) : ''}
}
}
`.trim()
}
const { attrsToQuery } = require('./utils')
const hotReloadAPIPath = JSON.stringify(require.resolve('vue-hot-reload-api'))
const nonWhitespaceRE = /\S+/
module.exports = function genStyleInjectionCode (
loaderContext,
styles,
id,
resourcePath,
stringifyRequest,
needsHotReload,
needsExplicitInjection
) {
let styleImportsCode = ``
let styleInjectionCode = ``
let cssModulesHotReloadCode = ``
let hasCSSModules = false
const cssModuleNames = new Map()
function genStyleRequest (style, i) {
const src = style.src || resourcePath
const attrsQuery = attrsToQuery(style.attrs, 'css')
const inheritQuery = `&${loaderContext.resourceQuery.slice(1)}`
// make sure to only pass id when necessary so that we don't inject
// duplicate tags when multiple components import the same css file
const idQuery = style.scoped ? `&id=${id}` : ``
const query = `?vue&type=style&index=${i}${idQuery}${attrsQuery}${inheritQuery}`
return stringifyRequest(src + query)
}
function genCSSModulesCode (style, request, i) {
hasCSSModules = true
const moduleName = style.module === true ? '$style' : style.module
if (cssModuleNames.has(moduleName)) {
loaderContext.emitError(`CSS module name ${moduleName} is not unique!`)
}
cssModuleNames.set(moduleName, true)
// `(vue-)style-loader` exports the name-to-hash map directly
// `css-loader` exports it in `.locals`
const locals = `(style${i}.locals || style${i})`
const name = JSON.stringify(moduleName)
if (!needsHotReload) {
styleInjectionCode += `this[${name}] = ${locals}\n`
} else {
styleInjectionCode += `
cssModules[${name}] = ${locals}
Object.defineProperty(this, ${name}, {
configurable: true,
get: function () {
return cssModules[${name}]
}
})
`
cssModulesHotReloadCode += `
module.hot && module.hot.accept([${request}], function () {
var oldLocals = cssModules[${name}]
if (oldLocals) {
var newLocals = require(${request})
if (JSON.stringify(newLocals) !== JSON.stringify(oldLocals)) {
cssModules[${name}] = newLocals
require(${hotReloadAPIPath}).rerender("${id}")
}
}
})
`
}
}
// empty styles: with no `src` specified or only contains whitespaces
const isNotEmptyStyle = style => style.src || nonWhitespaceRE.test(style.content)
// explicit injection is needed in SSR (for critical CSS collection)
// or in Shadow Mode (for injection into shadow root)
// In these modes, vue-style-loader exports objects with the __inject__
// method; otherwise we simply import the styles.
if (!needsExplicitInjection) {
styles.forEach((style, i) => {
// do not generate requests for empty styles
if (isNotEmptyStyle(style)) {
const request = genStyleRequest(style, i)
styleImportsCode += `import style${i} from ${request}\n`
if (style.module) genCSSModulesCode(style, request, i)
}
})
} else {
styles.forEach((style, i) => {
if (isNotEmptyStyle(style)) {
const request = genStyleRequest(style, i)
styleInjectionCode += (
`var style${i} = require(${request})\n` +
`if (style${i}.__inject__) style${i}.__inject__(context)\n`
)
if (style.module) genCSSModulesCode(style, request, i)
}
})
}
if (!needsExplicitInjection && !hasCSSModules) {
return styleImportsCode
}
return `
${styleImportsCode}
${hasCSSModules && needsHotReload ? `var cssModules = {}` : ``}
${needsHotReload ? `var disposed = false` : ``}
function injectStyles (context) {
${needsHotReload ? `if (disposed) return` : ``}
${styleInjectionCode}
}
${needsHotReload ? `
module.hot && module.hot.dispose(function (data) {
disposed = true
})
` : ``}
${cssModulesHotReloadCode}
`.trim()
}
const qs = require('querystring')
// these are built-in query parameters so should be ignored
// if the user happen to add them as attrs
const ignoreList = [
'id',
'index',
'src',
'type'
]
// transform the attrs on a SFC block descriptor into a resourceQuery string
exports.attrsToQuery = (attrs, langFallback) => {
let query = ``
for (const name in attrs) {
const value = attrs[name]
if (!ignoreList.includes(name)) {
query += `&${qs.escape(name)}=${value ? qs.escape(value) : ``}`
}
}
if (langFallback && !(`lang` in attrs)) {
query += `&lang=${langFallback}`
}
return query
}
import { Plugin } from 'webpack'
import { VueTemplateCompiler } from '@vue/component-compiler-utils/dist/types'
import { CompilerOptions } from 'vue-template-compiler'
declare namespace VueLoader {
class VueLoaderPlugin extends Plugin {}
interface VueLoaderOptions {
transformAssetUrls?: { [tag: string]: string | Array<string> }
compiler?: VueTemplateCompiler
compilerOptions?: CompilerOptions
transpileOptions?: Object
optimizeSSR?: boolean
hotReload?: boolean
productionMode?: boolean
shadowMode?: boolean
cacheDirectory?: string
cacheIdentifier?: string
prettify?: boolean
exposeFilename?: boolean
}
}
export = VueLoader
const path = require('path')
const hash = require('hash-sum')
const qs = require('querystring')
const plugin = require('./plugin')
const selectBlock = require('./select')
const loaderUtils = require('loader-utils')
const { attrsToQuery } = require('./codegen/utils')
const { parse } = require('@vue/component-compiler-utils')
const genStylesCode = require('./codegen/styleInjection')
const { genHotReloadCode } = require('./codegen/hotReload')
const genCustomBlocksCode = require('./codegen/customBlocks')
const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
const { NS } = require('./plugin')
let errorEmitted = false
function loadTemplateCompiler (loaderContext) {
try {
return require('vue-template-compiler')
} catch (e) {
if (/version mismatch/.test(e.toString())) {
loaderContext.emitError(e)
} else {
loaderContext.emitError(new Error(
`[vue-loader] vue-template-compiler must be installed as a peer dependency, ` +
`or a compatible compiler implementation must be passed via options.`
))
}
}
}
module.exports = function (source) {
const loaderContext = this
if (!errorEmitted && !loaderContext['thread-loader'] && !loaderContext[NS]) {
loaderContext.emitError(new Error(
`vue-loader was used without the corresponding plugin. ` +
`Make sure to include VueLoaderPlugin in your webpack config.`
))
errorEmitted = true
}
const stringifyRequest = r => loaderUtils.stringifyRequest(loaderContext, r)
const {
target,
request,
minimize,
sourceMap,
rootContext,
resourcePath,
resourceQuery = ''
} = loaderContext
const rawQuery = resourceQuery.slice(1)
const inheritQuery = `&${rawQuery}`
const incomingQuery = qs.parse(rawQuery)
const options = loaderUtils.getOptions(loaderContext) || {}
const isServer = target === 'node'
const isShadow = !!options.shadowMode
const isProduction = options.productionMode || minimize || process.env.NODE_ENV === 'production'
const filename = path.basename(resourcePath)
const context = rootContext || process.cwd()
const sourceRoot = path.dirname(path.relative(context, resourcePath))
const descriptor = parse({
source,
compiler: options.compiler || loadTemplateCompiler(loaderContext),
filename,
sourceRoot,
needMap: sourceMap
})
// if the query has a type field, this is a language block request
// e.g. foo.vue?type=template&id=xxxxx
// and we will return early
if (incomingQuery.type) {
return selectBlock(
descriptor,
loaderContext,
incomingQuery,
!!options.appendExtension
)
}
// module id for scoped CSS & hot-reload
const rawShortFilePath = path
.relative(context, resourcePath)
.replace(/^(\.\.[\/\\])+/, '')
const shortFilePath = rawShortFilePath.replace(/\\/g, '/') + resourceQuery
const id = hash(
isProduction
? (shortFilePath + '\n' + source.replace(/\r\n/g, '\n'))
: shortFilePath
)
// feature information
const hasScoped = descriptor.styles.some(s => s.scoped)
const hasFunctional = descriptor.template && descriptor.template.attrs.functional
const needsHotReload = (
!isServer &&
!isProduction &&
(descriptor.script || descriptor.template) &&
options.hotReload !== false
)
// template
let templateImport = `var render, staticRenderFns`
let templateRequest
if (descriptor.template) {
const src = descriptor.template.src || resourcePath
const idQuery = `&id=${id}`
const scopedQuery = hasScoped ? `&scoped=true` : ``
const attrsQuery = attrsToQuery(descriptor.template.attrs)
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
const request = templateRequest = stringifyRequest(src + query)
templateImport = `import { render, staticRenderFns } from ${request}`
}
// script
let scriptImport = `var script = {}`
if (descriptor.script) {
const src = descriptor.script.src || resourcePath
const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js')
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
const request = stringifyRequest(src + query)
scriptImport = (
`import script from ${request}\n` +
`export * from ${request}` // support named exports
)
}
// styles
let stylesCode = ``
if (descriptor.styles.length) {
stylesCode = genStylesCode(
loaderContext,
descriptor.styles,
id,
resourcePath,
stringifyRequest,
needsHotReload,
isServer || isShadow // needs explicit injection?
)
}
let code = `
${templateImport}
${scriptImport}
${stylesCode}
/* normalize component */
import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)}
var component = normalizer(
script,
render,
staticRenderFns,
${hasFunctional ? `true` : `false`},
${/injectStyles/.test(stylesCode) ? `injectStyles` : `null`},
${hasScoped ? JSON.stringify(id) : `null`},
${isServer ? JSON.stringify(hash(request)) : `null`}
${isShadow ? `,true` : ``}
)
`.trim() + `\n`
if (descriptor.customBlocks && descriptor.customBlocks.length) {
code += genCustomBlocksCode(
descriptor.customBlocks,
resourcePath,
resourceQuery,
stringifyRequest
)
}
if (needsHotReload) {
code += `\n` + genHotReloadCode(id, hasFunctional, templateRequest)
}
// Expose filename. This is used by the devtools and Vue runtime warnings.
if (!isProduction) {
// Expose the file's full path in development, so that it can be opened
// from the devtools.
code += `\ncomponent.options.__file = ${JSON.stringify(rawShortFilePath.replace(/\\/g, '/'))}`
} else if (options.exposeFilename) {
// Libraries can opt-in to expose their components' filenames in production builds.
// For security reasons, only expose the file's basename in production.
code += `\ncomponent.options.__file = ${JSON.stringify(filename)}`
}
code += `\nexport default component.exports`
return code
}
module.exports.VueLoaderPlugin = plugin
const qs = require('querystring')
const loaderUtils = require('loader-utils')
const hash = require('hash-sum')
const selfPath = require.resolve('../index')
const templateLoaderPath = require.resolve('./templateLoader')
const stylePostLoaderPath = require.resolve('./stylePostLoader')
const isESLintLoader = l => /(\/|\\|@)eslint-loader/.test(l.path)
const isNullLoader = l => /(\/|\\|@)null-loader/.test(l.path)
const isCSSLoader = l => /(\/|\\|@)css-loader/.test(l.path)
const isCacheLoader = l => /(\/|\\|@)cache-loader/.test(l.path)
const isPitcher = l => l.path !== __filename
const isPreLoader = l => !l.pitchExecuted
const isPostLoader = l => l.pitchExecuted
const dedupeESLintLoader = loaders => {
const res = []
let seen = false
loaders.forEach(l => {
if (!isESLintLoader(l)) {
res.push(l)
} else if (!seen) {
seen = true
res.push(l)
}
})
return res
}
const shouldIgnoreCustomBlock = loaders => {
const actualLoaders = loaders.filter(loader => {
// vue-loader
if (loader.path === selfPath) {
return false
}
// cache-loader
if (isCacheLoader(loader)) {
return false
}
return true
})
return actualLoaders.length === 0
}
module.exports = code => code
// This pitching loader is responsible for intercepting all vue block requests
// and transform it into appropriate requests.
module.exports.pitch = function (remainingRequest) {
const options = loaderUtils.getOptions(this)
const { cacheDirectory, cacheIdentifier } = options
const query = qs.parse(this.resourceQuery.slice(1))
let loaders = this.loaders
// if this is a language block request, eslint-loader may get matched
// multiple times
if (query.type) {
// if this is an inline block, since the whole file itself is being linted,
// remove eslint-loader to avoid duplicate linting.
if (/\.vue$/.test(this.resourcePath)) {
loaders = loaders.filter(l => !isESLintLoader(l))
} else {
// This is a src import. Just make sure there's not more than 1 instance
// of eslint present.
loaders = dedupeESLintLoader(loaders)
}
}
// remove self
loaders = loaders.filter(isPitcher)
// do not inject if user uses null-loader to void the type (#1239)
if (loaders.some(isNullLoader)) {
return
}
const genRequest = loaders => {
// Important: dedupe since both the original rule
// and the cloned rule would match a source import request.
// also make sure to dedupe based on loader path.
// assumes you'd probably never want to apply the same loader on the same
// file twice.
// Exception: in Vue CLI we do need two instances of postcss-loader
// for user config and inline minification. So we need to dedupe baesd on
// path AND query to be safe.
const seen = new Map()
const loaderStrings = []
loaders.forEach(loader => {
const identifier = typeof loader === 'string'
? loader
: (loader.path + loader.query)
const request = typeof loader === 'string' ? loader : loader.request
if (!seen.has(identifier)) {
seen.set(identifier, true)
// loader.request contains both the resolved loader path and its options
// query (e.g. ??ref-0)
loaderStrings.push(request)
}
})
return loaderUtils.stringifyRequest(this, '-!' + [
...loaderStrings,
this.resourcePath + this.resourceQuery
].join('!'))
}
// Inject style-post-loader before css-loader for scoped CSS and trimming
if (query.type === `style`) {
const cssLoaderIndex = loaders.findIndex(isCSSLoader)
if (cssLoaderIndex > -1) {
const afterLoaders = loaders.slice(0, cssLoaderIndex + 1)
const beforeLoaders = loaders.slice(cssLoaderIndex + 1)
const request = genRequest([
...afterLoaders,
stylePostLoaderPath,
...beforeLoaders
])
// console.log(request)
return query.module
? `export { default } from ${request}; export * from ${request}`
: `export * from ${request}`
}
}
// for templates: inject the template compiler & optional cache
if (query.type === `template`) {
const path = require('path')
const cacheLoader = cacheDirectory && cacheIdentifier
? [`${require.resolve('cache-loader')}?${JSON.stringify({
// For some reason, webpack fails to generate consistent hash if we
// use absolute paths here, even though the path is only used in a
// comment. For now we have to ensure cacheDirectory is a relative path.
cacheDirectory: (path.isAbsolute(cacheDirectory)
? path.relative(process.cwd(), cacheDirectory)
: cacheDirectory).replace(/\\/g, '/'),
cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
})}`]
: []
const preLoaders = loaders.filter(isPreLoader)
const postLoaders = loaders.filter(isPostLoader)
const request = genRequest([
...cacheLoader,
...postLoaders,
templateLoaderPath + `??vue-loader-options`,
...preLoaders
])
// console.log(request)
// the template compiler uses esm exports
return `export * from ${request}`
}
// if a custom block has no other matching loader other than vue-loader itself
// or cache-loader, we should ignore it
if (query.type === `custom` && shouldIgnoreCustomBlock(loaders)) {
return ``
}
// When the user defines a rule that has only resourceQuery but no test,
// both that rule and the cloned rule will match, resulting in duplicated
// loaders. Therefore it is necessary to perform a dedupe here.
const request = genRequest(loaders)
return `import mod from ${request}; export default mod; export * from ${request}`
}
const qs = require('querystring')
const { compileStyle } = require('@vue/component-compiler-utils')
// This is a post loader that handles scoped CSS transforms.
// Injected right before css-loader by the global pitcher (../pitch.js)
// for any <style scoped> selection requests initiated from within vue files.
module.exports = function (source, inMap) {
const query = qs.parse(this.resourceQuery.slice(1))
const { code, map, errors } = compileStyle({
source,
filename: this.resourcePath,
id: `data-v-${query.id}`,
map: inMap,
scoped: !!query.scoped,
trim: true
})
if (errors.length) {
this.callback(errors[0])
} else {
this.callback(null, code, map)
}
}
const qs = require('querystring')
const loaderUtils = require('loader-utils')
const { compileTemplate } = require('@vue/component-compiler-utils')
// Loader that compiles raw template into JavaScript functions.
// This is injected by the global pitcher (../pitch) for template
// selection requests initiated from vue files.
module.exports = function (source) {
const loaderContext = this
const query = qs.parse(this.resourceQuery.slice(1))
// although this is not the main vue-loader, we can get access to the same
// vue-loader options because we've set an ident in the plugin and used that
// ident to create the request for this loader in the pitcher.
const options = loaderUtils.getOptions(loaderContext) || {}
const { id } = query
const isServer = loaderContext.target === 'node'
const isProduction = options.productionMode || loaderContext.minimize || process.env.NODE_ENV === 'production'
const isFunctional = query.functional
// allow using custom compiler via options
const compiler = options.compiler || require('vue-template-compiler')
const compilerOptions = Object.assign({
outputSourceRange: true
}, options.compilerOptions, {
scopeId: query.scoped ? `data-v-${id}` : null,
comments: query.comments
})
// for vue-component-compiler
const finalOptions = {
source,
filename: this.resourcePath,
compiler,
compilerOptions,
// allow customizing behavior of vue-template-es2015-compiler
transpileOptions: options.transpileOptions,
transformAssetUrls: options.transformAssetUrls || true,
isProduction,
isFunctional,
optimizeSSR: isServer && options.optimizeSSR !== false,
prettify: options.prettify
}
const compiled = compileTemplate(finalOptions)
// tips
if (compiled.tips && compiled.tips.length) {
compiled.tips.forEach(tip => {
loaderContext.emitWarning(typeof tip === 'object' ? tip.msg : tip)
})
}
// errors
if (compiled.errors && compiled.errors.length) {
// 2.6 compiler outputs errors as objects with range
if (compiler.generateCodeFrame && finalOptions.compilerOptions.outputSourceRange) {
// TODO account for line offset in case template isn't placed at top
// of the file
loaderContext.emitError(
`\n\n Errors compiling template:\n\n` +
compiled.errors.map(({ msg, start, end }) => {
const frame = compiler.generateCodeFrame(source, start, end)
return ` ${msg}\n\n${pad(frame)}`
}).join(`\n\n`) +
'\n'
)
} else {
loaderContext.emitError(
`\n Error compiling template:\n${pad(compiled.source)}\n` +
compiled.errors.map(e => ` - ${e}`).join('\n') +
'\n'
)
}
}
const { code } = compiled
// finish with ESM exports
return code + `\nexport { render, staticRenderFns }`
}
function pad (source) {
return source
.split(/\r?\n/)
.map(line => ` ${line}`)
.join('\n')
}
const qs = require('querystring')
const RuleSet = require('webpack/lib/RuleSet')
const id = 'vue-loader-plugin'
const NS = 'vue-loader'
class VueLoaderPlugin {
apply (compiler) {
// add NS marker so that the loader can detect and report missing plugin
if (compiler.hooks) {
// webpack 4
compiler.hooks.compilation.tap(id, compilation => {
const normalModuleLoader = compilation.hooks.normalModuleLoader
normalModuleLoader.tap(id, loaderContext => {
loaderContext[NS] = true
})
})
} else {
// webpack < 4
compiler.plugin('compilation', compilation => {
compilation.plugin('normal-module-loader', loaderContext => {
loaderContext[NS] = true
})
})
}
// use webpack's RuleSet utility to normalize user rules
const rawRules = compiler.options.module.rules
const { rules } = new RuleSet(rawRules)
// find the rule that applies to vue files
let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`))
if (vueRuleIndex < 0) {
vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`))
}
const vueRule = rules[vueRuleIndex]
if (!vueRule) {
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.`
)
}
if (vueRule.oneOf) {
throw new Error(
`[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
)
}
// get the normlized "use" for vue files
const vueUse = vueRule.use
// get vue-loader options
const vueLoaderUseIndex = vueUse.findIndex(u => {
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]
vueLoaderUse.ident = 'vue-loader-options'
vueLoaderUse.options = vueLoaderUse.options || {}
// for each user rule (expect the vue rule), create a cloned rule
// that targets the corresponding language blocks in *.vue files.
const clonedRules = rules
.filter(r => r !== vueRule)
.map(cloneRule)
// global pitcher (responsible for injecting template compiler loader & CSS
// post loader)
const pitcher = {
loader: require.resolve('./loaders/pitcher'),
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
return parsed.vue != null
},
options: {
cacheDirectory: vueLoaderUse.options.cacheDirectory,
cacheIdentifier: vueLoaderUse.options.cacheIdentifier
}
}
// replace original rules
compiler.options.module.rules = [
pitcher,
...clonedRules,
...rules
]
}
}
function createMatcher (fakeFile) {
return (rule, i) => {
// #1201 we need to skip the `include` check when locating the vue rule
const clone = Object.assign({}, rule)
delete clone.include
const normalized = RuleSet.normalizeRule(clone, {}, '')
return (
!rule.enforce &&
normalized.resource &&
normalized.resource(fakeFile)
)
}
}
function cloneRule (rule) {
const { resource, resourceQuery } = rule
// Assuming `test` and `resourceQuery` tests are executed in series and
// synchronously (which is true based on RuleSet's implementation), we can
// save the current resource being matched from `test` so that we can access
// it in `resourceQuery`. This ensures when we use the normalized rule's
// resource check, include/exclude are matched correctly.
let currentResource
const res = Object.assign({}, rule, {
resource: {
test: resource => {
currentResource = resource
return true
}
},
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
if (parsed.vue == null) {
return false
}
if (resource && parsed.lang == null) {
return false
}
const fakeResourcePath = `${currentResource}.${parsed.lang}`
if (resource && !resource(fakeResourcePath)) {
return false
}
if (resourceQuery && !resourceQuery(query)) {
return false
}
return true
}
})
if (rule.rules) {
res.rules = rule.rules.map(cloneRule)
}
if (rule.oneOf) {
res.oneOf = rule.oneOf.map(cloneRule)
}
return res
}
VueLoaderPlugin.NS = NS
module.exports = VueLoaderPlugin
const qs = require('querystring')
const id = 'vue-loader-plugin'
const NS = 'vue-loader'
const BasicEffectRulePlugin = require('webpack/lib/rules/BasicEffectRulePlugin')
const BasicMatcherRulePlugin = require('webpack/lib/rules/BasicMatcherRulePlugin')
const DescriptionDataMatcherRulePlugin = require('webpack/lib/rules/DescriptionDataMatcherRulePlugin')
const RuleSetCompiler = require('webpack/lib/rules/RuleSetCompiler')
const UseEffectRulePlugin = require('webpack/lib/rules/UseEffectRulePlugin')
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 DescriptionDataMatcherRulePlugin(),
new BasicEffectRulePlugin('type'),
new BasicEffectRulePlugin('sideEffects'),
new BasicEffectRulePlugin('parser'),
new BasicEffectRulePlugin('resolve'),
new BasicEffectRulePlugin('generator'),
new UseEffectRulePlugin()
])
class VueLoaderPlugin {
apply (compiler) {
const normalModule = compiler.webpack
? compiler.webpack.NormalModule
: require('webpack/lib/NormalModule')
// add NS marker so that the loader can detect and report missing plugin
compiler.hooks.compilation.tap(id, compilation => {
const normalModuleLoader = normalModule.getCompilationHooks(compilation).loader
normalModuleLoader.tap(id, loaderContext => {
loaderContext[NS] = true
})
})
const rules = compiler.options.module.rules
let rawVueRules
let vueRules = []
for (const rawRule of rules) {
// skip rules with 'enforce'. eg. rule for eslint-loader
if (rawRule.enforce) {
continue
}
// skip the `include` check when locating the vue rule
const clonedRawRule = Object.assign({}, rawRule)
delete clonedRawRule.include
const ruleSet = ruleSetCompiler.compile([{
rules: [clonedRawRule]
}])
vueRules = ruleSet.exec({
resource: 'foo.vue'
})
if (!vueRules.length) {
vueRules = ruleSet.exec({
resource: 'foo.vue.html'
})
}
if (vueRules.length > 0) {
if (rawRule.oneOf) {
throw new Error(
`[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
)
}
rawVueRules = 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 normlized "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 => {
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]
vueLoaderUse.ident = 'vue-loader-options'
vueLoaderUse.options = vueLoaderUse.options || {}
// for each user rule (expect 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 !== rawVueRules)
.map((rawRule) => cloneRule(rawRule, refs))
// fix conflict with config.loader and config.options when using config.use
delete rawVueRules.loader
delete rawVueRules.options
rawVueRules.use = vueUse
// global pitcher (responsible for injecting template compiler loader & CSS
// post loader)
const pitcher = {
loader: require.resolve('./loaders/pitcher'),
resourceQuery: query => {
if (!query) { return false }
const parsed = qs.parse(query.slice(1))
return parsed.vue != null
},
options: {
cacheDirectory: vueLoaderUse.options.cacheDirectory,
cacheIdentifier: vueLoaderUse.options.cacheIdentifier
}
}
// replace original rules
compiler.options.module.rules = [
pitcher,
...clonedRules,
...rules
]
}
}
let uid = 0
function cloneRule (rawRule, refs) {
const rules = ruleSetCompiler.compileRules(`clonedRuleSet-${++uid}`, [{
rules: [rawRule]
}], refs)
let currentResource
const conditions = rules[0].rules
.map(rule => rule.conditions)
// shallow flat
.reduce((prev, next) => prev.concat(next), [])
// do not process rule with enforce
if (!rawRule.enforce) {
const ruleUse = rules[0].rules
.map(rule => rule.effects
.filter(effect => effect.type === 'use')
.map(effect => effect.value)
)
// shallow flat
.reduce((prev, next) => prev.concat(next), [])
// fix conflict with config.loader and config.options when using config.use
delete rawRule.loader
delete rawRule.options
rawRule.use = ruleUse
}
const res = 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 (!conditions) {
return false
}
const fakeResourcePath = `${currentResource}.${parsed.lang}`
for (const condition of 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))
}
if (rawRule.oneOf) {
res.oneOf = rawRule.oneOf.map(rule => cloneRule(rule, refs))
}
return res
}
VueLoaderPlugin.NS = NS
module.exports = VueLoaderPlugin
const webpack = require('webpack')
let VueLoaderPlugin = null
if (webpack.version && webpack.version[0] > 4) {
// webpack5 and upper
VueLoaderPlugin = require('./plugin-webpack5')
} else {
// webpack4 and lower
VueLoaderPlugin = require('./plugin-webpack4')
}
module.exports = VueLoaderPlugin
/* globals __VUE_SSR_CONTEXT__ */
// IMPORTANT: Do NOT use ES2015 features in this file (except for modules).
// This module is a runtime utility for cleaner component module output and will
// be included in the final webpack user bundle.
export default function normalizeComponent (
scriptExports,
render,
staticRenderFns,
functionalTemplate,
injectStyles,
scopeId,
moduleIdentifier, /* server only */
shadowMode /* vue-cli only */
) {
// Vue.extend constructor export interop
var options = typeof scriptExports === 'function'
? scriptExports.options
: scriptExports
// render functions
if (render) {
options.render = render
options.staticRenderFns = staticRenderFns
options._compiled = true
}
// functional template
if (functionalTemplate) {
options.functional = true
}
// scopedId
if (scopeId) {
options._scopeId = 'data-v-' + scopeId
}
var hook
if (moduleIdentifier) { // server build
hook = function (context) {
// 2.3 injection
context =
context || // cached call
(this.$vnode && this.$vnode.ssrContext) || // stateful
(this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
// 2.2 with runInNewContext: true
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
context = __VUE_SSR_CONTEXT__
}
// inject component styles
if (injectStyles) {
injectStyles.call(this, context)
}
// register component module identifier for async chunk inferrence
if (context && context._registeredComponents) {
context._registeredComponents.add(moduleIdentifier)
}
}
// used by ssr in case component is cached and beforeCreate
// never gets called
options._ssrRegister = hook
} else if (injectStyles) {
hook = shadowMode
? function () {
injectStyles.call(
this,
(options.functional ? this.parent : this).$root.$options.shadowRoot
)
}
: injectStyles
}
if (hook) {
if (options.functional) {
// for template-only hot-reload because in that case the render fn doesn't
// go through the normalizer
options._injectStyles = hook
// register for functional component in vue file
var originalRender = options.render
options.render = function renderWithStyleInjection (h, context) {
hook.call(context)
return originalRender(h, context)
}
} else {
// inject component registration as beforeCreate hook
var existing = options.beforeCreate
options.beforeCreate = existing
? [].concat(existing, hook)
: [hook]
}
}
return {
exports: scriptExports,
options: options
}
}
module.exports = function selectBlock (
descriptor,
loaderContext,
query,
appendExtension
) {
// template
if (query.type === `template`) {
if (appendExtension) {
loaderContext.resourcePath += '.' + (descriptor.template.lang || 'html')
}
loaderContext.callback(
null,
descriptor.template.content,
descriptor.template.map
)
return
}
// script
if (query.type === `script`) {
if (appendExtension) {
loaderContext.resourcePath += '.' + (descriptor.script.lang || 'js')
}
loaderContext.callback(
null,
descriptor.script.content,
descriptor.script.map
)
return
}
// styles
if (query.type === `style` && query.index != null) {
const style = descriptor.styles[query.index]
if (appendExtension) {
loaderContext.resourcePath += '.' + (style.lang || 'css')
}
loaderContext.callback(
null,
style.content,
style.map
)
return
}
// custom
if (query.type === 'custom' && query.index != null) {
const block = descriptor.customBlocks[query.index]
loaderContext.callback(
null,
block.content,
block.map
)
return
}
}
The MIT License (MIT)
Copyright (c) 2015-present, songsiqi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# Weex `<style>` Transformer
[![NPM version][npm-image]][npm-url]
[![Build status][circle-image]][circle-url]
[![Downloads][downloads-image]][downloads-url]
[npm-image]: https://img.shields.io/npm/v/weex-styler.svg?style=flat-square
[npm-url]: https://npmjs.org/package/weex-styler
[circle-image]: https://circleci.com/gh/alibaba/weex_toolchain.svg?style=svg
[circle-url]: https://circleci.com/gh/alibaba/weex_toolchain/tree/master
[downloads-image]: https://img.shields.io/npm/dm/weex-styler.svg?style=flat-square
[downloads-url]: https://npmjs.org/package/weex-styler
## Features
- convert a `<style>` element to JSON format
- autofix common mistakes
- friendly warnings
## API
- `parse(code, done)`
- `validate(json, done)`
- `validateItem(name, value)`
### util api
- `util.hyphenedToCamelCase(value)`
- `util.camelCaseToHyphened(value)`
```javascript
/**
* Parse `<style>` code to a JSON Object and log errors & warnings
*
* @param {string} code
* @param {function} done
*/
function parse(code, done) {}
/**
* Validate a JSON Object and log errors & warnings
*
* @param {object} json
* @param {function} done
*/
function validate(json, done) {}
/**
* Result callback
*
* data
* - jsonStyle{}: `classname.propname.value`-like object
* - log[{line, column, reason}]
*
* @param {Error} err
* @param {object} data
*/
function done(err, data) {}
/**
* Validate a single name-value pair
*
* @param {string} name camel cased
* @param {string} value
* @return {object}
* - value
* - log{reason}
*/
function validateItem(name, value) {}
```
## Validation
- rule check: only common rule type supported, othres will be ignored
- selector check: only single-classname selector is supported, others will be ignored
- prop name check: out-of-defined prop name will be warned but preserved
- prop value check: common prop value mistakes will be autofixed or ignored
+ color type: keywords, `#xxx` -> warning: `#xxxxxx`
+ color type: `transparent` -> error: not supported
+ length type: `100px` -> warning: `100`
## Demo
```javascript
var styler = require('weex-styler')
var code = 'html {color: #000000;} .foo {color: red; -webkit-transform: rotate(90deg); width: 200px;}'
styler.parse(code, function (err, data) {
// syntax error
// format: {line, column, reason, ...}
err
// result
// {foo: {color: '#ff0000', webkitTransform: 'rotate(90deg)', width: 200}}
data.jsonStyle
// format: {line, column, reason}
// - Error: Selector `html` is not supported. Weex only support single-classname selector
// - Warning: prop value `red` is autofixed to `#ff0000`
// - Warning: prop name `-webkit-transform` is not supported
// - Warning: prop value `200px` is autofixed to `200`
data.log[]
})
var jsonStyle = {
foo: {
color: 'red',
webkitTransform: 'rotate(90deg)',
width: '200px'
}
}
styler.validate(json, function (err, data) {
// syntax error
err
// result
// {foo: {color: '#ff0000', webkitTransform: 'rotate(90deg)', width: 200}}
data.jsonStyle
// format: {reason}
// - Warning: prop value `red` is autofixed to `#ff0000`
// - Warning: prop name `-webkit-transform` is not supported
// - Warning: prop value `200px` is autofixed to `200`
data.log[]
})
```
var gulp = require('gulp')
var jscs = require('gulp-jscs')
var mocha = require('gulp-mocha')
gulp.task('default', ['test'], function() {
console.log('done')
})
gulp.task('watch', function () {
return gulp.watch('**/*.js', ['test'])
})
gulp.task('test', ['jscs', 'mocha'], function () {
console.log('test done')
})
gulp.task('mocha', function () {
return gulp.src([
'test/*.js'
]).pipe(mocha())
})
gulp.task('jscs', function () {
return gulp.src('**/*.js').pipe(jscs())
})
'use strict'
var css = require('css')
var util = require('./lib/util')
var validateItem = require('./lib/validator').validate
var shorthandParser = require('./lib/shorthand-parser')
// padding & margin shorthand parsing
function convertLengthShorthand (rule, prop) {
for (var i = 0; i < rule.declarations.length; i++) {
var declaration = rule.declarations[i]
if (declaration.property === prop) {
var values = declaration.value.split(/\s+/)
// values[0] = values[0] || 0
values[1] = values[1] || values[0]
values[2] = values[2] || values[0]
values[3] = values[3] || values[1]
rule.declarations.splice(i, 1)
rule.declarations.splice(i, 0, { type: 'declaration', property: prop + '-left', value: values[3], position: declaration.position })
rule.declarations.splice(i, 0, { type: 'declaration', property: prop + '-bottom', value: values[2], position: declaration.position })
rule.declarations.splice(i, 0, { type: 'declaration', property: prop + '-right', value: values[1], position: declaration.position })
rule.declarations.splice(i, 0, { type: 'declaration', property: prop + '-top', value: values[0], position: declaration.position })
// break
}
}
}
/**
* mergeStyle
* @param {*} object
* @param {*} classNames
* @param {*} preClassNames
* @param {*} ruleResult
* @param {*} prop
* @param {*} index
*/
function mergeStyle (object, classNames, preClassNames, ruleResult, prop, index) {
if (!process.env.UNI_USING_NVUE_STYLE_COMPILER) {
object[classNames] = object[classNames] || {}
object[classNames][prop] = ruleResult[prop]
return
}
classNames = classNames.split('.').map(str => '.' + str).slice(1)
var className = classNames.find(className => className in object) || classNames[0]
// 假设选择器已经去重简化
preClassNames += classNames.filter(str => str !== className).sort().join('')
var rules = object[className] = object[className] || {}
var style = rules[preClassNames] = rules[preClassNames] || {}
// 增加其他权重信息
style[prop] = [...ruleResult[prop], preClassNames.split('.').length - 1, index]
}
/**
* Parse `<style>` code to a JSON Object and log errors & warnings
*
* @param {string} code
* @param {function} done which will be called with
* - err:Error
* - data.jsonStyle{}: `classname.propname.value`-like object
* - data.log[{line, column, reason}]
*/
function parse (code, done) {
var ast, err, jsonStyle = {}, log = []
// css parse
ast = css.parse(code, { silent: true })
// catch syntax error
if (ast.stylesheet.parsingErrors && ast.stylesheet.parsingErrors.length) {
err = ast.stylesheet.parsingErrors
err.forEach(function (error) {
log.push({ line: error.line, column: error.column, reason: error.toString().replace('Error', 'ERROR') })
})
}
// walk all
/* istanbul ignore else */
if (ast && ast.type === 'stylesheet' && ast.stylesheet &&
ast.stylesheet.rules && ast.stylesheet.rules.length) {
ast.stylesheet.rules.forEach(function (rule, index) {
var type = rule.type
var ruleResult = {}
var ruleLog = []
if (type === 'rule') {
if (rule.declarations && rule.declarations.length) {
rule.declarations = shorthandParser(rule.declarations)
// padding & margin shorthand parsing
convertLengthShorthand(rule, 'padding')
convertLengthShorthand(rule, 'margin')
rule.declarations.forEach(function (declaration) {
var subType = declaration.type
var name, value, line, column, subResult, camelCasedName
/* istanbul ignore if */
if (subType !== 'declaration') {
return
}
name = declaration.property
value = declaration.value
var newValue = value.replace(/\s*!important/g, '')
var importantWeight = Number(value !== newValue)
value = newValue
// validate declarations and collect them to result
camelCasedName = util.hyphenedToCamelCase(name)
subResult = validateItem(camelCasedName, value)
/* istanbul ignore else */
if (typeof subResult.value === 'number' || typeof subResult.value === 'string') {
if (process.env.UNI_USING_NVUE_STYLE_COMPILER) {
var oldValue = ruleResult[camelCasedName]
// 增加 important 权重信息
ruleResult[camelCasedName] = Array.isArray(oldValue) && oldValue[1] > importantWeight ? oldValue : [subResult.value, importantWeight]
} else {
ruleResult[camelCasedName] = subResult.value
}
}
if (subResult.log) {
subResult.log.line = declaration.position.start.line
subResult.log.column = declaration.position.start.column
ruleLog.push(subResult.log)
}
})
rule.selectors.forEach(function (selector) {
selector = selector.replace(/\s*([\+\~\>])\s*/g, '$1').replace(/\s+/, ' ')
// 支持组合选择器
const res = selector.match(process.env.UNI_USING_NVUE_STYLE_COMPILER ? /^((?:(?:\.[A-Za-z0-9_\-]+)+[\+\~\> ])*)((?:\.[A-Za-z0-9_\-\:]+)+)$/ : /^(\.)([A-Za-z0-9_\-:]+)$/)
if (res) {
var preClassNames = res[1]
var classNames = res[2]
// handle pseudo class
var pseudoIndex = classNames.indexOf(':')
if (pseudoIndex > -1) {
var pseudoCls = classNames.slice(pseudoIndex)
classNames = classNames.slice(0, pseudoIndex)
var pseudoRuleResult = {}
Object.keys(ruleResult).forEach(function (prop) {
pseudoRuleResult[prop + pseudoCls] = ruleResult[prop]
})
ruleResult = pseudoRuleResult
}
// merge style
Object.keys(ruleResult).forEach(function (prop) {
// // handle transition
// if (prop.indexOf('transition') === 0 && prop !== 'transition') {
// var realProp = prop.replace('transition', '')
// realProp = realProp[0].toLowerCase() + realProp.slice(1)
// var object = jsonStyle['@TRANSITION'] = jsonStyle['@TRANSITION'] || {}
// mergeStyle(object, classNames, preClassNames, ruleResult, prop, index)
// }
mergeStyle(jsonStyle, classNames, preClassNames, ruleResult, prop, index)
})
}
else {
log.push({
line: rule.position.start.line,
column: rule.position.start.column,
reason: 'ERROR: Selector `' + selector + '` is not supported. Weex only support classname selector'
})
}
})
log = log.concat(ruleLog)
}
}
/* istanbul ignore else */
else if (type === 'font-face') {
/* istanbul ignore else */
if (rule.declarations && rule.declarations.length) {
rule.declarations.forEach(function (declaration) {
/* istanbul ignore if */
if (declaration.type !== 'declaration') {
return
}
var name = util.hyphenedToCamelCase(declaration.property)
var value = declaration.value
if (name === 'fontFamily' && '\"\''.indexOf(value[0]) > -1) { // FIXME: delete leading and trailing quotes
value = value.slice(1, value.length - 1)
}
ruleResult[name] = value
})
if (!jsonStyle['@FONT-FACE']) {
jsonStyle['@FONT-FACE'] = []
}
jsonStyle['@FONT-FACE'].push(ruleResult)
}
}
})
}
jsonStyle['@VERSION'] = 2
done(err, { jsonStyle: jsonStyle, log: log })
}
/**
* Validate a JSON Object and log errors & warnings
*
* @param {object} json
* @param {function} done which will be called with
* - err:Error
* - data.jsonStyle{}: `classname.propname.value`-like object
* - data.log[{reason}]
*/
function validate (json, done) {
var log = []
var err
try {
json = JSON.parse(JSON.stringify(json))
}
catch (e) {
err = e
json = {}
}
Object.keys(json).forEach(function (selector) {
var declarations = json[selector]
Object.keys(declarations).forEach(function (name) {
var value = declarations[name]
var result = validateItem(name, value)
if (typeof result.value === 'number' || typeof result.value === 'string') {
declarations[name] = result.value
}
else {
delete declarations[name]
}
if (result.log) {
log.push(result.log)
}
})
})
done(err, {
jsonStyle: json,
log: log
})
}
module.exports = {
parse: parse,
validate: validate,
validateItem: validateItem,
util: util
}
function generateDeclaration (property, value, important, position) {
return {
type: 'declaration',
property,
value: value + (important ? ' !important' : ''),
position
}
}
function clearImportant (value) {
var newValue = value.replace(/\s*!important/g, '')
return {
value: newValue,
important: value !== newValue
}
}
function transition (declaration) {
var CHUNK_REGEXP = /^(\S*)?\s*(\d*\.?\d+(?:ms|s)?)?\s*(\S*)?\s*(\d*\.?\d+(?:ms|s)?)?$/
var { value, important } = clearImportant(declaration.value)
var match = value.match(CHUNK_REGEXP)
var result = []
var position = declaration.position
match[1] && result.push(generateDeclaration('transition-property', match[1], important, position))
match[2] && result.push(generateDeclaration('transition-duration', match[2], important, position))
match[3] && result.push(generateDeclaration('transition-timing-function', match[3], important, position))
match[4] && result.push(generateDeclaration('transition-delay', match[4], important, position))
return result
}
function margin (declaration) {
var { value, important } = clearImportant(declaration.value)
var position = declaration.position
var splitResult = value.split(/\s+/)
var result = []
switch (splitResult.length) {
case 1:
splitResult.push(splitResult[0], splitResult[0], splitResult[0])
break
case 2:
splitResult.push(splitResult[0], splitResult[1])
break
case 3:
splitResult.push(splitResult[1])
break
}
result.push(
generateDeclaration('margin-top', splitResult[0], important, position),
generateDeclaration('margin-right', splitResult[1], important, position),
generateDeclaration('margin-bottom', splitResult[2], important, position),
generateDeclaration('margin-left', splitResult[3], important, position)
)
return result
}
function border (declaration) {
var { value, important } = clearImportant(declaration.value)
var property = declaration.property
var position = declaration.position
var splitResult = value.replace(/\s*,\s*/g, ',').split(/\s+/)
var result = [/^[\d\.]+\S*$/, /^(solid|dashed|dotted)$/, /\S+/].map(item => {
var index = splitResult.findIndex(str => item.test(str))
return index < 0 ? null : splitResult.splice(index, 1)[0]
})
if (splitResult.length) {
return declaration
}
return [
generateDeclaration(property + '-width', (result[0] || '0').trim(), important, position),
generateDeclaration(property + '-style', (result[1] || 'solid').trim(), important, position),
generateDeclaration(property + '-color', (result[2] || '#000000').trim(), important, position)
]
}
function borderProperty (declaration) {
var { value, important } = clearImportant(declaration.value)
var position = declaration.position
var property = declaration.property.split('-')[1]
var splitResult = value.replace(/\s*,\s*/g, ',').split(/\s+/)
var result = []
switch (splitResult.length) {
case 1:
return declaration
case 2:
splitResult.push(splitResult[0], splitResult[1])
break
case 3:
splitResult.push(splitResult[1])
break
}
result.push(
generateDeclaration('border-top-' + property, splitResult[0], important, position),
generateDeclaration('border-right-' + property, splitResult[1], important, position),
generateDeclaration('border-bottom-' + property, splitResult[2], important, position),
generateDeclaration('border-left-' + property, splitResult[3], important, position)
)
return result
}
function borderRadius (declaration) {
var { value, important } = clearImportant(declaration.value)
var position = declaration.position
var splitResult = value.split(/\s+/)
var result = []
if (value.includes('/')) {
return declaration
}
switch (splitResult.length) {
case 1:
return declaration
case 2:
splitResult.push(splitResult[0], splitResult[1])
break
case 3:
splitResult.push(splitResult[1])
break
}
result.push(
generateDeclaration('border-top-left-radius', splitResult[0], important, position),
generateDeclaration('border-top-right-radius', splitResult[1], important, position),
generateDeclaration('border-bottom-right-radius', splitResult[2], important, position),
generateDeclaration('border-bottom-left-radius', splitResult[3], important, position)
)
return result
}
function flexFlow (declaration) {
var { value, important } = clearImportant(declaration.value)
var position = declaration.position
var splitResult = value.split(/\s+/)
var result = [/^(column|column-reverse|row|row-reverse)$/, /^(nowrap|wrap|wrap-reverse)$/].map(item => {
var index = splitResult.findIndex(str => item.test(str))
return index < 0 ? null : splitResult.splice(index, 1)[0]
})
if (splitResult.length) {
return declaration
}
return [
generateDeclaration('flex-direction', result[0] || 'column', important, position),
generateDeclaration('flex-wrap', result[1] || 'nowrap', important, position)
]
}
function font (declaration) {
var { value, important } = clearImportant(declaration.value)
var position = declaration.position
var splitResult = value.replace(/,\s*/g, ',').replace(/\s*\/\s*/, '/').replace(/['"].+?['"]/g, str => str.replace(/\s+/g, '#')).split(/\s+/)
var result = []
var styleValues = ['normal', 'italic', 'oblique']
result.push(generateDeclaration('font-style', styleValues[Math.max(0, styleValues.indexOf(splitResult[0]))], important, position))
var weight = splitResult.slice(0, -2).find(str => /normal|bold|lighter|bolder|\d+/.test(str))
if (weight) {
result.push(generateDeclaration('font-weight', weight, important, position))
}
splitResult = splitResult.slice(-2)
if (/[\d\.]+\S*(\/[\d\.]+\S*)?/.test(splitResult[0])) {
var [size, height] = splitResult[0].split('/')
result.push(
generateDeclaration('font-size', size, important, position),
generateDeclaration('line-height', height || 'normal', important, position),
generateDeclaration('font-family', splitResult[1].replace(/#/g, ' '), important, position)
)
return result
}
return []
}
function background (declaration) {
var { value, important } = clearImportant(declaration.value)
var position = declaration.position
if (/^#?\S+$/.test(value) || /^rgba?(.+)$/.test(value)) {
return generateDeclaration('background-color', value, important, position)
} else if (/^linear-gradient(.+)$/.test(value)) {
return generateDeclaration('background-image', value, important, position)
} else {
return declaration
}
}
var parserCollection = {
transition,
margin,
border,
'border-top': border,
'border-right': border,
'border-bottom': border,
'border-left': border,
'border-style': borderProperty,
'border-width': borderProperty,
'border-color': borderProperty,
'border-radius': borderRadius,
'flex-flow': flexFlow,
font,
background
}
module.exports = function (declarations) {
return declarations.reduce((result, declaration) => {
var parser = parserCollection[declaration.property]
if (parser) {
return result.concat(parser(declaration))
} else {
result.push(declaration)
return result
}
}, [])
}
/**
* rules:
* - abc-def -> abcDef
* - -abc-def -> AbcDef
*
* @param {string} value
* @return {string}
*/
exports.hyphenedToCamelCase = function hyphenedToCamelCase(value) {
return value.replace(/-([a-z])/g, function(s, m) {
return m.toUpperCase()
})
}
/**
* rules:
* - abcDef -> abc-def
* - AbcDef -> -abc-def
*
* @param {string} value
* @return {string}
*/
exports.camelCaseToHyphened = function camelCaseToHyphened(value) {
return value.replace(/([A-Z])/g, function(s, m) {
if (typeof m === 'string') {
return '-' + m.toLowerCase()
}
return m
})
}
此差异已折叠。
{
"_args": [
[
"weex-styler@0.3.1",
"/Users/fxy/Documents/DCloud/HBuilderX/uniapp-cli"
]
],
"_development": true,
"_from": "weex-styler@0.3.1",
"_id": "weex-styler@0.3.1",
"_inBundle": false,
"_integrity": "sha512-xkX5/wS/QLiJXKwbdpeytbLN0kHviQwj9CLdvBxqu+RRZABZpTniKZr1oxjh9Q0+n/aRC+smwFpQpUKvXh9V1g==",
"_location": "/weex-styler",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "weex-styler@0.3.1",
"name": "weex-styler",
"escapedName": "weex-styler",
"rawSpec": "0.3.1",
"saveSpec": null,
"fetchSpec": "0.3.1"
},
"_requiredBy": [
"/weex-vue-loader"
],
"_resolved": "https://registry.npmjs.org/weex-styler/-/weex-styler-0.3.1.tgz",
"_spec": "0.3.1",
"_where": "/Users/fxy/Documents/DCloud/HBuilderX/uniapp-cli",
"author": {
"name": "songsiqi",
"email": "songsiqi2006@126.com"
},
"bugs": {
"url": "https://github.com/weexteam/weex-styler/issues"
},
"dependencies": {
"css": "~2.2.1"
},
"description": "Weex <style> transformer",
"devDependencies": {
"chai": "~3.4.1",
"gulp": "~3.9.0",
"gulp-jscs": "~3.0.2",
"gulp-mocha": "~2.2.0",
"isparta": "~4.0.0",
"sinon": "~1.17.2",
"sinon-chai": "~2.8.0"
},
"homepage": "https://github.com/weexteam/weex-styler#readme",
"keywords": [
"weex"
],
"license": "MIT",
"main": "index.js",
"name": "weex-styler",
"repository": {
"type": "git",
"url": "git+https://github.com/weexteam/weex-styler.git"
},
"scripts": {
"cover": "node node_modules/isparta/bin/isparta cover node_modules/gulp-mocha/node_modules/.bin/_mocha -- --reporter dot",
"test": "gulp test && npm run cover"
},
"version": "0.3.1"
}
var chai = require('chai')
var sinon = require('sinon')
var sinonChai = require('sinon-chai')
var expect = chai.expect
chai.use(sinonChai)
var styler = require('../')
describe('parse', function () {
it('parse normal style code', function (done) {
var code = 'html {color: #000000;}\n\n.foo {color: red; background-color: rgba(255,255,255,0.6); -webkit-transform: rotate(90deg); width: 200px; left: 0; right: 0px; border-width: 1pt; font-weight: 100}\n\n.bar {background: red}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({foo: {color: '#FF0000', backgroundColor: 'rgba(255,255,255,0.6)', WebkitTransform: 'rotate(90deg)', width: '200px', left: 0, right: '0px', borderWidth: '1pt', fontWeight: '100'}, bar: {background: 'red'}})
expect(data.log).eql([
{line: 1, column: 1, reason: 'ERROR: Selector `html` is not supported. Weex only support single-classname selector'},
{line: 3, column: 7, reason: 'NOTE: property value `red` is autofixed to `#FF0000`'},
{line: 3, column: 60, reason: 'WARNING: `-webkit-transform` is not a standard property name (may not be supported)'},
{line: 5, column: 7, reason: 'WARNING: `background` is not a standard property name (may not be supported), suggest `background-color`'}
])
done()
})
})
it('parse and fix prop value', function (done) {
var code = '.foo {font-size: 200px;}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({foo: {fontSize: '200px'}})
done()
})
})
it('parse and ensure number type value', function (done) {
var code = '.foo {line-height: 40;}\n\n .bar {line-height: 20px;}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({foo: {lineHeight: 40}, bar: {lineHeight: '20px'}})
done()
})
})
it('handle complex class definition', function (done) {
var code = '.foo, .bar {font-size: 20;}\n\n .foo {color: #ff5000;}\n\n .bar {color: #000000;}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
foo: {fontSize: 20, color: '#ff5000'},
bar: {fontSize: 20, color: '#000000'}
})
done()
})
})
it('handle more complex class definition', function (done) {
var code = '.foo, .bar {font-size: 20; color: #000000}\n\n .foo, .bar, .baz {color: #ff5000; height: 30;}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
foo: {fontSize: 20, color: '#ff5000', height: 30},
bar: {fontSize: 20, color: '#ff5000', height: 30},
baz: {color: '#ff5000', height: 30}
})
done()
})
})
it('parse transition', function (done) {
var code = '.foo {transition-property: margin-top; transition-duration: 300ms; transition-delay: 0.2s; transition-timing-function: ease-in;}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle['@TRANSITION']).eql({foo: {property: 'marginTop', duration: 300, delay: 200, timingFunction: 'ease-in'}})
expect(data.jsonStyle.foo).eql({
transitionDelay: 200,
transitionDuration: 300,
transitionProperty: "marginTop",
transitionTimingFunction: "ease-in"
})
expect(data.log).eql([
{line: 1, column: 40, reason: 'NOTE: property value `300ms` is autofixed to `300`'},
{line: 1, column: 68, reason: 'NOTE: property value `0.2s` is autofixed to `200`'}
])
done()
})
})
it('parse transition transform', function (done) {
var code = '.foo {transition-property: transform; transition-duration: 300ms; transition-delay: 0.2s; transition-timing-function: ease-in-out;}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle['@TRANSITION']).eql({foo: {property: 'transform', duration: 300, delay: 200, timingFunction: 'ease-in-out'}})
expect(data.jsonStyle.foo).eql({
transitionDelay: 200,
transitionDuration: 300,
transitionProperty: "transform",
transitionTimingFunction: "ease-in-out"
})
done()
})
})
it('parse multi transition properties', function (done) {
var code = '.foo {transition-property: margin-top, height; transition-duration: 300ms; transition-delay: 0.2s; transition-timing-function: ease-in-out;}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle['@TRANSITION']).eql({foo: {property: 'marginTop,height', duration: 300, delay: 200, timingFunction: 'ease-in-out'}})
expect(data.jsonStyle.foo).eql({
transitionDelay: 200,
transitionDuration: 300,
transitionProperty: "marginTop,height",
transitionTimingFunction: "ease-in-out"
})
done()
})
})
it('parse complex transition', function (done) {
var code = '.foo {font-size: 20; color: #000000}\n\n .foo, .bar {color: #ff5000; height: 30; transition-property: margin-top; transition-duration: 300ms; transition-delay: 0.2s; transition-timing-function: ease-in;}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle['@TRANSITION']).eql({
foo: {property: 'marginTop', duration: 300, delay: 200, timingFunction: 'ease-in'},
bar: {property: 'marginTop', duration: 300, delay: 200, timingFunction: 'ease-in'}
})
expect(data.jsonStyle.foo).eql({
fontSize: 20, color: '#ff5000', height: 30,
transitionDelay: 200,
transitionDuration: 300,
transitionProperty: "marginTop",
transitionTimingFunction: "ease-in"
})
expect(data.jsonStyle.bar).eql({
color: '#ff5000', height: 30,
transitionDelay: 200,
transitionDuration: 300,
transitionProperty: "marginTop",
transitionTimingFunction: "ease-in"
})
expect(data.log).eql([
{line: 3, column: 75, reason: 'NOTE: property value `300ms` is autofixed to `300`'},
{line: 3, column: 103, reason: 'NOTE: property value `0.2s` is autofixed to `200`'}
])
done()
})
})
it('parse transition shorthand', function (done) {
var code = '.foo {font-size: 20; transition: margin-top 500ms ease-in-out 1s}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle['@TRANSITION']).eql({foo: {property: 'marginTop', duration: 500, delay: 1000, timingFunction: 'ease-in-out' }})
expect(data.jsonStyle.foo).eql({
fontSize: 20,
transitionDelay: 1000,
transitionDuration: 500,
transitionProperty: "marginTop",
transitionTimingFunction: "ease-in-out"
})
expect(data.log).eql([
{line: 1, column: 22, reason: 'NOTE: property value `500ms` is autofixed to `500`'},
{line: 1, column: 22, reason: 'NOTE: property value `1s` is autofixed to `1000`'}
])
done()
})
})
it.skip('override transition shorthand', function (done) {
var code = '.foo {font-size: 32px; transition: margin-top 500ms ease-in-out 1s; transition-duration: 300ms}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle['@TRANSITION']).eql({foo: {property: 'marginTop', duration: 300, delay: 1000, timingFunction: 'ease-in-out' }})
expect(data.jsonStyle.foo).eql({
fontSize: 32,
transitionDelay: 1000,
transitionDuration: 300,
transitionProperty: "marginTop",
transitionTimingFunction: "ease-in-out"
})
done()
})
})
it('parse padding & margin shorthand', function (done) {
var code = '.foo { padding: 20px; margin: 30px 40; } .bar { margin: 10px 20 30; padding: 10 20px 30px 40;}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle.foo).eql({
paddingTop: '20px',
paddingRight: '20px',
paddingBottom: '20px',
paddingLeft: '20px',
marginTop: '30px',
marginRight: 40,
marginBottom: '30px',
marginLeft: 40
})
expect(data.jsonStyle.bar).eql({
paddingTop: 10,
paddingRight: '20px',
paddingBottom: '30px',
paddingLeft: 40,
marginTop: '10px',
marginRight: 20,
marginBottom: 30,
marginLeft: 20
})
done()
})
})
it('override padding & margin shorthand', function (done) {
var code = '.foo { padding: 20px; padding-left: 30px; } .bar { margin: 10px 20; margin-bottom: 30px;}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle.foo).eql({
paddingTop: '20px',
paddingRight: '20px',
paddingBottom: '20px',
paddingLeft: '30px'
})
expect(data.jsonStyle.bar).eql({
marginTop: '10px',
marginRight: 20,
marginBottom: '30px',
marginLeft: 20
})
done()
})
})
it('handle pseudo class', function (done) {
var code = '.class-a {color: #0000ff;} .class-a:last-child:focus {color: #ff0000;}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
'class-a': {
color: '#0000ff',
'color:last-child:focus': '#ff0000'
}
})
done()
})
})
it('handle iconfont', function (done) {
var code = '@font-face {font-family: "font-family-name-1"; src: url("font file url 1-1") format("truetype");} @font-face {font-family: "font-family-name-2"; src: url("font file url 2-1") format("truetype"), url("font file url 2-2") format("woff");}'
styler.parse(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
'@FONT-FACE': [
{fontFamily: 'font-family-name-1', src: 'url("font file url 1-1") format("truetype")'},
{fontFamily: 'font-family-name-2', src: 'url("font file url 2-1") format("truetype"), url("font file url 2-2") format("woff")'}
]
})
done()
})
})
it('handle syntax error', function (done) {
var code = 'asdf'
styler.parse(code, function (err, data) {
expect(err).is.an.array
expect(err[0].toString()).eql('Error: undefined:1:5: missing \'{\'')
expect(err[0].reason).eql('missing \'{\'')
expect(err[0].filename).eql(undefined)
expect(err[0].line).eql(1)
expect(err[0].column).eql(5)
expect(err[0].source).eql('')
expect(data.log).eql([{line: 1, column: 5, reason: 'ERROR: undefined:1:5: missing \'{\''}])
done()
})
})
})
var chai = require('chai')
var sinon = require('sinon')
var sinonChai = require('sinon-chai')
var expect = chai.expect
chai.use(sinonChai)
var shorthandParser = require('../lib/shorthand-parser')
describe('shorthand-parser', function () {
it('parse transition', function () {
var declarations = [
{
type: 'declaration',
property: 'transition',
value: 'margin-top 500ms ease-in-out 1s',
position: {}
}
]
var result = shorthandParser(declarations)
expect(result).eql([
{
type: 'declaration',
property: 'transition-property',
value: 'margin-top',
position: {}
},
{
type: 'declaration',
property: 'transition-duration',
value: '500ms',
position: {}
},
{
type: 'declaration',
property: 'transition-timing-function',
value: 'ease-in-out',
position: {}
},
{
type: 'declaration',
property: 'transition-delay',
value: '1s',
position: {}
}
])
})
it('parse margin', function () {
var declarations = [
{
type: 'declaration',
property: 'margin',
value: '1px',
position: {}
},
{
type: 'declaration',
property: 'margin',
value: '21px 22px',
position: {}
},
{
type: 'declaration',
property: 'margin',
value: '31px 32px 33px',
position: {}
},
{
type: 'declaration',
property: 'margin',
value: '41px 42px 43px 44px',
position: {}
}
]
var result = shorthandParser(declarations)
expect(result).eql([
{
type: 'declaration',
property: 'margin-top',
value: '1px',
position: {}
},
{
type: 'declaration',
property: 'margin-right',
value: '1px',
position: {}
},
{
type: 'declaration',
property: 'margin-bottom',
value: '1px',
position: {}
},
{
type: 'declaration',
property: 'margin-left',
value: '1px',
position: {}
},
{
type: 'declaration',
property: 'margin-top',
value: '21px',
position: {}
},
{
type: 'declaration',
property: 'margin-right',
value: '22px',
position: {}
},
{
type: 'declaration',
property: 'margin-bottom',
value: '21px',
position: {}
},
{
type: 'declaration',
property: 'margin-left',
value: '22px',
position: {}
},
{
type: 'declaration',
property: 'margin-top',
value: '31px',
position: {}
},
{
type: 'declaration',
property: 'margin-right',
value: '32px',
position: {}
},
{
type: 'declaration',
property: 'margin-bottom',
value: '33px',
position: {}
},
{
type: 'declaration',
property: 'margin-left',
value: '32px',
position: {}
},
{
type: 'declaration',
property: 'margin-top',
value: '41px',
position: {}
},
{
type: 'declaration',
property: 'margin-right',
value: '42px',
position: {}
},
{
type: 'declaration',
property: 'margin-bottom',
value: '43px',
position: {}
},
{
type: 'declaration',
property: 'margin-left',
value: '44px',
position: {}
}
])
})
})
var chai = require('chai')
var sinon = require('sinon')
var sinonChai = require('sinon-chai')
var expect = chai.expect
chai.use(sinonChai)
var styler = require('../')
describe('validate', function () {
it('parse normal style code', function (done) {
var code = {
foo: {
color: '#FF0000',
width: '200',
position: 'sticky',
zIndex: 4
}
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({foo: {color: '#FF0000', width: 200, position: 'sticky', zIndex: 4}})
expect(data.log).eql([])
done()
})
})
it('parse length', function (done) {
var code = {
foo: {
width: '200px',
paddingLeft: '300',
borderWidth: '1pt',
left: '0',
right: '0px',
marginRight: 'asdf'
}
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({foo: {
width: '200px',
paddingLeft: 300,
borderWidth: '1pt',
left: 0,
right: '0px'
}})
expect(data.log).eql([
{reason: 'ERROR: property value `asdf` is not supported for `margin-right` (only number and pixel values are supported)'}
])
done()
})
})
it('parse number', function (done) {
var code = {
foo: {
opacity: '1'
},
bar: {
opacity: '0.5'
},
baz: {
opacity: 'a'
},
boo: {
opacity: '0.5a'
},
zero: {
opacity: '0'
}
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
foo: {
opacity: 1
},
bar: {
opacity: 0.5
},
baz: {},
boo: {},
zero: {
opacity: 0
}
})
expect(data.log).eql([
{reason: 'ERROR: property value `a` is not supported for `opacity` (only number is supported)'},
{reason: 'ERROR: property value `0.5a` is not supported for `opacity` (only number is supported)'}
])
done()
})
})
it('parse integer', function (done) {
var code = {
foo: {
zIndex: '1'
},
bar: {
zIndex: '0.5'
},
baz: {
zIndex: 'a'
},
boo: {
zIndex: '0.5a'
},
zero: {
zIndex: '0'
}
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
foo: {
zIndex: 1
},
bar: {},
baz: {},
boo: {},
zero: {
zIndex: 0
}
})
expect(data.log).eql([
{reason: 'ERROR: property value `0.5` is not supported for `z-index` (only integer is supported)'},
{reason: 'ERROR: property value `a` is not supported for `z-index` (only integer is supported)'},
{reason: 'ERROR: property value `0.5a` is not supported for `z-index` (only integer is supported)'}
])
done()
})
})
it('parse color', function (done) {
var code = {
foo: {
color: '#FF0000',
backgroundColor: '#ff0000'
},
bar: {
color: '#F00',
backgroundColor: '#f00'
},
baz: {
color: 'red',
backgroundColor: 'lightpink'
},
rgba: {
color: 'rgb(23, 0, 255)',
backgroundColor: 'rgba(234, 45, 99, .4)'
},
transparent: {
color: 'transparent',
backgroundColor: 'asdf'
},
errRgba: {
color: 'rgb(266,0,255)',
backgroundColor: 'rgba(234,45,99,1.3)'
}
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
foo: {
color: '#FF0000',
backgroundColor: '#ff0000'
},
bar: {
color: '#FF0000',
backgroundColor: '#ff0000'
},
baz: {
color: '#FF0000',
backgroundColor: '#FFB6C1'
},
rgba: {
color: 'rgb(23,0,255)',
backgroundColor: 'rgba(234,45,99,0.4)'
},
transparent: {
color: 'rgba(0,0,0,0)'
},
errRgba: {}
})
expect(data.log).eql([
{reason: 'NOTE: property value `#F00` is autofixed to `#FF0000`'},
{reason: 'NOTE: property value `#f00` is autofixed to `#ff0000`'},
{reason: 'NOTE: property value `red` is autofixed to `#FF0000`'},
{reason: 'NOTE: property value `lightpink` is autofixed to `#FFB6C1`'},
{reason: 'ERROR: property value `asdf` is not valid for `background-color`'},
{reason: 'ERROR: property value `rgb(266,0,255)` is not valid for `color`'},
{reason: 'ERROR: property value `rgba(234,45,99,1.3)` is not valid for `background-color`'}
])
done()
})
})
it('parse color', function (done) {
var code = {
foo: {
color: '#FF0000',
backgroundColor: '#ff0000'
},
bar: {
color: '#F00',
backgroundColor: '#f00'
},
baz: {
color: 'red',
backgroundColor: 'lightpink'
},
rgba: {
color: 'rgb(23, 0, 255)',
backgroundColor: 'rgba(234, 45, 99, .4)'
},
transparent: {
color: 'transparent',
backgroundColor: 'asdf'
},
errRgba: {
color: 'rgb(266,0,255)',
backgroundColor: 'rgba(234,45,99,1.3)'
}
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
foo: {
color: '#FF0000',
backgroundColor: '#ff0000'
},
bar: {
color: '#FF0000',
backgroundColor: '#ff0000'
},
baz: {
color: '#FF0000',
backgroundColor: '#FFB6C1'
},
rgba: {
color: 'rgb(23,0,255)',
backgroundColor: 'rgba(234,45,99,0.4)'
},
transparent: {
color: 'rgba(0,0,0,0)'
},
errRgba: {}
})
expect(data.log).eql([
{reason: 'NOTE: property value `#F00` is autofixed to `#FF0000`'},
{reason: 'NOTE: property value `#f00` is autofixed to `#ff0000`'},
{reason: 'NOTE: property value `red` is autofixed to `#FF0000`'},
{reason: 'NOTE: property value `lightpink` is autofixed to `#FFB6C1`'},
{reason: 'ERROR: property value `asdf` is not valid for `background-color`'},
{reason: 'ERROR: property value `rgb(266,0,255)` is not valid for `color`'},
{reason: 'ERROR: property value `rgba(234,45,99,1.3)` is not valid for `background-color`'}
])
done()
})
})
it('parse flex-wrap', function (done) {
var code = {
foo: { flexWrap: 'nowrap' },
bar: { flexWrap: 'wrap' }
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
foo: { flexWrap: 'nowrap' },
bar: { flexWrap: 'wrap' }
})
expect(data.log).eql([
{reason: 'NOTE: property value `nowrap` is the DEFAULT value for `flex-wrap` (could be removed)'},
{reason: 'NOTE: the flex-wrap property may have compatibility problem on native'},
])
done()
})
})
it('parse transition-property', function (done) {
var code = {
foo: {
transitionProperty: 'margin-top'
},
bar: {
transitionProperty: 'height'
},
foobar: {
transitionProperty: 'margin-top, height'
},
baz: {
transitionProperty: 'abc'
}
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
foo: {
transitionProperty: 'marginTop'
},
bar: {
transitionProperty: 'height'
},
foobar: {
transitionProperty: 'marginTop,height'
},
baz: {}
})
expect(data.log).eql([
{reason: 'ERROR: property value `abc` is not supported for `transition-property` (only css property is valid)'}
])
done()
})
})
it('parse transition-duration & transition-delay', function (done) {
var code = {
foo: {
transitionDuration: '200ms',
transitionDelay: '0.5s'
},
bar: {
transitionDuration: '200',
transitionDelay: 'abc'
}
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
foo: {
transitionDuration: 200,
transitionDelay: 500
},
bar: {
transitionDuration: 200
}
})
expect(data.log).eql([
{reason: 'NOTE: property value `200ms` is autofixed to `200`'},
{reason: 'NOTE: property value `0.5s` is autofixed to `500`'},
{reason: 'ERROR: property value `abc` is not supported for `transition-delay` (only number of seconds and milliseconds is valid)'}
])
done()
})
})
it('parse transition-timing-function', function (done) {
var code = {
foo: {
transitionTimingFunction: 'ease-in-out'
},
bar: {
transitionTimingFunction: 'cubic-bezier(.88, 1.0, -0.67, 1.37)'
},
baz: {
transitionTimingFunction: 'abc'
}
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
foo: {
transitionTimingFunction: 'ease-in-out'
},
bar: {
transitionTimingFunction: 'cubic-bezier(0.88,1,-0.67,1.37)'
},
baz: {}
})
expect(data.log).eql([
{reason: 'ERROR: property value `abc` is not supported for `transition-timing-function` (supported values are: `linear`|`ease`|`ease-in`|`ease-out`|`ease-in-out`|`cubic-bezier(n,n,n,n)`)'}
])
done()
})
})
it('parse unknown', function (done) {
var code = {
foo: {
background: '#ff0000',
abc: '123',
def: '456px',
ghi: '789pt',
AbcDef: '456',
abcDef: 'abc'
}
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({
foo: {
background: '#ff0000',
abc: 123,
def: '456px',
ghi: '789pt',
AbcDef: 456,
abcDef: 'abc'
}
})
expect(data.log).eql([
{reason: 'WARNING: `background` is not a standard property name (may not be supported), suggest `background-color`'},
{reason: 'WARNING: `abc` is not a standard property name (may not be supported)'},
{reason: 'WARNING: `def` is not a standard property name (may not be supported)'},
{reason: 'WARNING: `ghi` is not a standard property name (may not be supported)'},
{reason: 'WARNING: `-abc-def` is not a standard property name (may not be supported)'},
{reason: 'WARNING: `abc-def` is not a standard property name (may not be supported)'}
])
done()
})
})
it('parse complex style code', function (done) {
var code = {
foo: {
color: 'red',
WebkitTransform: 'rotate(90deg)',
width: '200px'
}
}
styler.validate(code, function (err, data) {
expect(err).is.undefined
expect(data).is.an.object
expect(data.jsonStyle).eql({foo: {color: '#FF0000', WebkitTransform: 'rotate(90deg)', width: '200px'}})
expect(data.log).eql([
{reason: 'NOTE: property value `red` is autofixed to `#FF0000`'},
{reason: 'WARNING: `-webkit-transform` is not a standard property name (may not be supported)'}
])
done()
})
})
})
# weex-template-compiler
> This package is auto-generated. For pull requests please see [src/platforms/weex/entry-compiler.js](https://github.com/vuejs/vue/tree/dev/src/platforms/weex/entry-compiler.js).
try {
var vueVersion = require('weex-vue-framework').version
} catch (e) {}
var packageName = require('./package.json').name
var packageVersion = require('./package.json').version
if (vueVersion && vueVersion !== packageVersion) {
throw new Error(
'\n\nVue packages version mismatch:\n\n' +
'- vue@' + vueVersion + '\n' +
'- ' + packageName + '@' + packageVersion + '\n\n' +
'This may cause things to work incorrectly. Make sure to use the same version for both.\n' +
'If you are using weex-vue-loader, re-installing them should bump ' + packageName + ' to the latest.\n'
)
}
module.exports = require('./build')
{
"name": "weex-template-compiler",
"version": "2.6.10-weex.1",
"description": "Weex template compiler for Vue 2.0",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
},
"keywords": [
"vue",
"compiler"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue/issues"
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/weex-template-compiler#readme",
"dependencies": {
"acorn": "^5.2.1",
"escodegen": "^1.8.1",
"he": "^1.1.0"
}
}
import { RuleSetRule } from 'webpack'
export const babelLoader: RuleSetRule = {
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
babelrc: false,
},
},
],
}
import { RuleSetRule } from 'webpack'
export const vueLoader: RuleSetRule = {
test: [/\.nvue(\?[^?]+)?$/, /\.vue(\?[^?]+)?$/],
use: [
{
loader: 'vue-loader',
options: {
compiler: require('../../../../../lib/weex-template-compiler'),
},
},
],
}
import { LoaderContext } from 'webpack'
function pageLoader(this: LoaderContext<{}>, content: string) {}
export default pageLoader
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册