UNPKG

17 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6try {
7 require.resolve('@vue/compiler-sfc');
8}
9catch (e) {
10 throw new Error('rollup-plugin-vue requires @vue/compiler-sfc to be present in the dependency ' +
11 'tree.');
12}
13const compiler_sfc_1 = require("@vue/compiler-sfc");
14const fs_1 = __importDefault(require("fs"));
15const debug_1 = __importDefault(require("debug"));
16const hash_sum_1 = __importDefault(require("hash-sum"));
17const path_1 = require("path");
18const querystring_1 = __importDefault(require("querystring"));
19const rollup_pluginutils_1 = require("rollup-pluginutils");
20const debug = debug_1.default('rollup-plugin-vue');
21const defaultOptions = {
22 include: /\.vue$/,
23 exclude: [],
24 target: 'browser',
25 exposeFilename: false,
26 customBlocks: [],
27};
28function PluginVue(userOptions = {}) {
29 const options = {
30 ...defaultOptions,
31 ...userOptions,
32 };
33 const isServer = options.target === 'node';
34 const isProduction = process.env.NODE_ENV === 'production' || process.env.BUILD === 'production';
35 const rootContext = process.cwd();
36 const filter = rollup_pluginutils_1.createFilter(options.include, options.exclude);
37 const filterCustomBlock = createCustomBlockFilter(options.customBlocks);
38 return {
39 name: 'vue',
40 async resolveId(id, importer) {
41 const query = parseVuePartRequest(id);
42 if (query.vue) {
43 if (query.src) {
44 const resolved = await this.resolve(query.filename, importer, {
45 skipSelf: true,
46 });
47 if (resolved) {
48 cache.set(resolved.id, getDescriptor(importer));
49 const [, originalQuery] = id.split('?', 2);
50 resolved.id += `?${originalQuery}`;
51 return resolved;
52 }
53 }
54 else if (!filter(query.filename)) {
55 return undefined;
56 }
57 debug(`resolveId(${id})`);
58 return id;
59 }
60 return undefined;
61 },
62 load(id) {
63 const query = parseVuePartRequest(id);
64 if (query.vue) {
65 if (query.src) {
66 return fs_1.default.readFileSync(query.filename, 'utf-8');
67 }
68 const descriptor = getDescriptor(query.filename);
69 if (descriptor) {
70 const block = query.type === 'template'
71 ? descriptor.template
72 : query.type === 'script'
73 ? descriptor.script
74 : query.type === 'style'
75 ? descriptor.styles[query.index]
76 : typeof query.index === 'number'
77 ? descriptor.customBlocks[query.index]
78 : null;
79 if (block) {
80 return {
81 code: block.content,
82 map: normalizeSourceMap(block.map),
83 };
84 }
85 }
86 }
87 return undefined;
88 },
89 async transform(code, id) {
90 const query = parseVuePartRequest(id);
91 if (query.vue) {
92 if (!query.src && !filter(query.filename))
93 return null;
94 const descriptor = getDescriptor(query.filename);
95 const hasScoped = descriptor.styles.some((s) => s.scoped);
96 if (query.type === 'template') {
97 debug(`transform(${id})`);
98 const block = descriptor.template;
99 const result = compiler_sfc_1.compileTemplate({
100 filename: query.filename,
101 source: code,
102 inMap: query.src ? undefined : block.map,
103 preprocessLang: block.lang,
104 preprocessCustomRequire: options.preprocessCustomRequire,
105 compiler: options.compiler,
106 ssr: isServer,
107 compilerOptions: {
108 ...options.compilerOptions,
109 scopeId: hasScoped ? `data-v-${query.id}` : undefined,
110 bindingMetadata: descriptor.script
111 ? descriptor.script.bindings
112 : undefined,
113 },
114 transformAssetUrls: options.transformAssetUrls,
115 });
116 if (result.errors.length) {
117 result.errors.forEach((error) => this.error(typeof error === 'string'
118 ? { id: query.filename, message: error }
119 : createRollupError(query.filename, error)));
120 return null;
121 }
122 if (result.tips.length) {
123 result.tips.forEach((tip) => this.warn({
124 id: query.filename,
125 message: tip,
126 }));
127 }
128 return {
129 code: result.code,
130 map: normalizeSourceMap(result.map),
131 };
132 }
133 else if (query.type === 'style') {
134 debug(`transform(${id})`);
135 const block = descriptor.styles[query.index];
136 const result = await compiler_sfc_1.compileStyleAsync({
137 filename: query.filename,
138 id: `data-v-${query.id}`,
139 source: code,
140 scoped: block.scoped,
141 vars: !!block.vars,
142 modules: !!block.module,
143 postcssOptions: options.postcssOptions,
144 postcssPlugins: options.postcssPlugins,
145 modulesOptions: options.cssModulesOptions,
146 preprocessLang: options.preprocessStyles
147 ? block.lang
148 : undefined,
149 preprocessCustomRequire: options.preprocessCustomRequire,
150 preprocessOptions: options.preprocessOptions || {},
151 });
152 if (result.errors.length) {
153 result.errors.forEach((error) => this.error({
154 id: query.filename,
155 message: error.message,
156 }));
157 return null;
158 }
159 if (query.module) {
160 return {
161 code: `export default ${_(result.modules)}`,
162 map: null,
163 };
164 }
165 else {
166 return {
167 code: result.code,
168 map: normalizeSourceMap(result.map),
169 };
170 }
171 }
172 return null;
173 }
174 else if (filter(id)) {
175 debug(`transform(${id})`);
176 const { descriptor, errors } = parseSFC(code, id, rootContext);
177 if (errors.length) {
178 errors.forEach((error) => this.error(createRollupError(id, error)));
179 return null;
180 }
181 // module id for scoped CSS & hot-reload
182 const output = transformVueSFC(code, id, descriptor, { rootContext, isProduction, isServer, filterCustomBlock }, options);
183 debug('transient .vue file:', '\n' + output + '\n');
184 return {
185 code: output,
186 map: {
187 mappings: '',
188 },
189 };
190 }
191 else {
192 return null;
193 }
194 },
195 };
196}
197exports.default = PluginVue;
198function createCustomBlockFilter(queries) {
199 if (!queries || queries.length === 0)
200 return () => false;
201 const allowed = new Set(queries.filter((query) => /^[a-z]/i.test(query)));
202 const disallowed = new Set(queries
203 .filter((query) => /^![a-z]/i.test(query))
204 .map((query) => query.substr(1)));
205 const allowAll = queries.includes('*') || !queries.includes('!*');
206 return (type) => {
207 if (allowed.has(type))
208 return true;
209 if (disallowed.has(type))
210 return true;
211 return allowAll;
212 };
213}
214function parseVuePartRequest(id) {
215 const [filename, query] = id.split('?', 2);
216 if (!query)
217 return { vue: false, filename };
218 const raw = querystring_1.default.parse(query);
219 if ('vue' in raw) {
220 return {
221 ...raw,
222 filename,
223 vue: true,
224 index: Number(raw.index),
225 src: 'src' in raw,
226 scoped: 'scoped' in raw,
227 };
228 }
229 return { vue: false, filename };
230}
231const cache = new Map();
232function getDescriptor(id) {
233 if (cache.has(id)) {
234 return cache.get(id);
235 }
236 throw new Error(`${id} is not parsed yet`);
237}
238function parseSFC(code, id, sourceRoot) {
239 const { descriptor, errors } = compiler_sfc_1.parse(code, {
240 sourceMap: true,
241 filename: id,
242 sourceRoot: sourceRoot,
243 });
244 cache.set(id, descriptor);
245 return { descriptor, errors };
246}
247function transformVueSFC(code, resourcePath, descriptor, { rootContext, isProduction, isServer, filterCustomBlock, }, options) {
248 const shortFilePath = path_1.relative(rootContext, resourcePath)
249 .replace(/^(\.\.[\/\\])+/, '')
250 .replace(/\\/g, '/');
251 const id = hash_sum_1.default(isProduction ? shortFilePath + '\n' + code : shortFilePath);
252 // feature information
253 const hasScoped = descriptor.styles.some((s) => s.scoped);
254 const templateImport = getTemplateCode(descriptor, resourcePath, id, hasScoped, isServer);
255 const scriptImport = getScriptCode(descriptor, resourcePath);
256 const stylesCode = getStyleCode(descriptor, resourcePath, id, options.preprocessStyles);
257 const customBlocksCode = getCustomBlock(descriptor, resourcePath, filterCustomBlock);
258 const output = [
259 scriptImport,
260 templateImport,
261 stylesCode,
262 customBlocksCode,
263 isServer ? `script.ssrRender = ssrRender` : `script.render = render`,
264 ];
265 if (hasScoped) {
266 output.push(`script.__scopeId = ${_(`data-v-${id}`)}`);
267 }
268 if (!isProduction) {
269 output.push(`script.__file = ${_(shortFilePath)}`);
270 }
271 else if (options.exposeFilename) {
272 output.push(`script.__file = ${_(path_1.basename(shortFilePath))}`);
273 }
274 output.push('export default script');
275 return output.join('\n');
276}
277function getTemplateCode(descriptor, resourcePath, id, hasScoped, isServer) {
278 let templateImport = `const render = () => {}`;
279 let templateRequest;
280 if (descriptor.template) {
281 const src = descriptor.template.src || resourcePath;
282 const idQuery = `&id=${id}`;
283 const scopedQuery = hasScoped ? `&scoped=true` : ``;
284 const srcQuery = descriptor.template.src ? `&src` : ``;
285 const attrsQuery = attrsToQuery(descriptor.template.attrs);
286 const query = `?vue&type=template${idQuery}${srcQuery}${scopedQuery}${attrsQuery}`;
287 templateRequest = _(src + query);
288 templateImport = `import { ${isServer ? 'ssrRender' : 'render'} } from ${templateRequest}`;
289 }
290 return templateImport;
291}
292function getScriptCode(descriptor, resourcePath) {
293 let scriptImport = `const script = {}`;
294 if (descriptor.script || descriptor.scriptSetup) {
295 descriptor.script = compiler_sfc_1.compileScript(descriptor);
296 const src = descriptor.script.src || resourcePath;
297 const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js');
298 const srcQuery = descriptor.script.src ? `&src` : ``;
299 const query = `?vue&type=script${srcQuery}${attrsQuery}`;
300 const scriptRequest = _(src + query);
301 scriptImport =
302 `import script from ${scriptRequest}\n` + `export * from ${scriptRequest}`; // support named exports
303 }
304 return scriptImport;
305}
306function getStyleCode(descriptor, resourcePath, id, preprocessStyles) {
307 let stylesCode = ``;
308 let hasCSSModules = false;
309 if (descriptor.styles.length) {
310 descriptor.styles.forEach((style, i) => {
311 const src = style.src || resourcePath;
312 // do not include module in default query, since we use it to indicate
313 // that the module needs to export the modules json
314 const attrsQuery = attrsToQuery(style.attrs, 'css', preprocessStyles);
315 const attrsQueryWithoutModule = attrsQuery.replace(/&module(=true|=[^&]+)?/, '');
316 // make sure to only pass id when necessary so that we don't inject
317 // duplicate tags when multiple components import the same css file
318 const idQuery = style.scoped ? `&id=${id}` : ``;
319 const srcQuery = style.src ? `&src` : ``;
320 const query = `?vue&type=style&index=${i}${srcQuery}${idQuery}`;
321 const styleRequest = src + query + attrsQuery;
322 const styleRequestWithoutModule = src + query + attrsQueryWithoutModule;
323 if (style.module) {
324 if (!hasCSSModules) {
325 stylesCode += `\nconst cssModules = script.__cssModules = {}`;
326 hasCSSModules = true;
327 }
328 stylesCode += genCSSModulesCode(id, i, styleRequest, styleRequestWithoutModule, style.module);
329 }
330 else {
331 stylesCode += `\nimport ${_(styleRequest)}`;
332 }
333 // TODO SSR critical CSS collection
334 });
335 }
336 return stylesCode;
337}
338function getCustomBlock(descriptor, resourcePath, filter) {
339 let code = '';
340 descriptor.customBlocks.forEach((block, index) => {
341 if (filter(block.type)) {
342 const src = block.src || resourcePath;
343 const attrsQuery = attrsToQuery(block.attrs, block.type);
344 const srcQuery = block.src ? `&src` : ``;
345 const query = `?vue&type=${block.type}&index=${index}${srcQuery}${attrsQuery}`;
346 const request = _(src + query);
347 code += `import block${index} from ${request}\n`;
348 code += `if (typeof block${index} === 'function') block${index}(script)\n`;
349 }
350 });
351 return code;
352}
353function createRollupError(id, error) {
354 return {
355 id,
356 plugin: 'vue',
357 pluginCode: String(error.code),
358 message: error.message,
359 frame: error.loc.source,
360 parserError: error,
361 loc: error.loc
362 ? {
363 file: id,
364 line: error.loc.start.line,
365 column: error.loc.start.column,
366 }
367 : undefined,
368 };
369}
370// these are built-in query parameters so should be ignored
371// if the user happen to add them as attrs
372const ignoreList = ['id', 'index', 'src', 'type', 'lang'];
373function attrsToQuery(attrs, langFallback, forceLangFallback = false) {
374 let query = ``;
375 for (const name in attrs) {
376 const value = attrs[name];
377 if (!ignoreList.includes(name)) {
378 query += `&${querystring_1.default.escape(name)}${value ? `=${querystring_1.default.escape(String(value))}` : ``}`;
379 }
380 }
381 if (langFallback || attrs.lang) {
382 query +=
383 `lang` in attrs
384 ? forceLangFallback
385 ? `&lang.${langFallback}`
386 : `&lang.${attrs.lang}`
387 : `&lang.${langFallback}`;
388 }
389 return query;
390}
391function _(any) {
392 return JSON.stringify(any);
393}
394function normalizeSourceMap(map) {
395 if (!map)
396 return null;
397 return {
398 ...map,
399 version: Number(map.version),
400 mappings: typeof map.mappings === 'string' ? map.mappings : '',
401 };
402}
403function genCSSModulesCode(
404// @ts-ignore
405id, index, request, requestWithoutModule, moduleName) {
406 const styleVar = `style${index}`;
407 let code =
408 // first import the CSS for extraction
409 `\nimport ${_(requestWithoutModule)}` +
410 // then import the json file to expose to component...
411 `\nimport ${styleVar} from ${_(request + '.js')}`;
412 // inject variable
413 const name = typeof moduleName === 'string' ? moduleName : '$style';
414 code += `\ncssModules["${name}"] = ${styleVar}`;
415 return code;
416}
417// overwrite for cjs require('rollup-plugin-vue')() usage
418module.exports = PluginVue;