index.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. try {
  7. require.resolve('@vue/compiler-sfc');
  8. }
  9. catch (e) {
  10. throw new Error('rollup-plugin-vue requires @vue/compiler-sfc to be present in the dependency ' +
  11. 'tree.');
  12. }
  13. const compiler_sfc_1 = require("@vue/compiler-sfc");
  14. const fs_1 = __importDefault(require("fs"));
  15. const debug_1 = __importDefault(require("debug"));
  16. const hash_sum_1 = __importDefault(require("hash-sum"));
  17. const path_1 = require("path");
  18. const querystring_1 = __importDefault(require("querystring"));
  19. const rollup_pluginutils_1 = require("rollup-pluginutils");
  20. const debug = debug_1.default('rollup-plugin-vue');
  21. const defaultOptions = {
  22. include: /\.vue$/,
  23. exclude: [],
  24. target: 'browser',
  25. exposeFilename: false,
  26. customBlocks: [],
  27. };
  28. function 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, id),
  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.src) {
  97. this.addWatchFile(query.filename);
  98. }
  99. if (query.type === 'template') {
  100. debug(`transform(${id})`);
  101. const block = descriptor.template;
  102. const preprocessLang = block.lang;
  103. const preprocessOptions = preprocessLang &&
  104. options.templatePreprocessOptions &&
  105. options.templatePreprocessOptions[preprocessLang];
  106. const result = compiler_sfc_1.compileTemplate({
  107. filename: query.filename,
  108. source: code,
  109. inMap: query.src ? undefined : block.map,
  110. preprocessLang,
  111. preprocessOptions,
  112. preprocessCustomRequire: options.preprocessCustomRequire,
  113. compiler: options.compiler,
  114. ssr: isServer,
  115. compilerOptions: {
  116. ...options.compilerOptions,
  117. scopeId: hasScoped ? `data-v-${query.id}` : undefined,
  118. bindingMetadata: descriptor.script
  119. ? descriptor.script.bindings
  120. : undefined,
  121. },
  122. transformAssetUrls: options.transformAssetUrls,
  123. });
  124. if (result.errors.length) {
  125. result.errors.forEach((error) => this.error(typeof error === 'string'
  126. ? { id: query.filename, message: error }
  127. : createRollupError(query.filename, error)));
  128. return null;
  129. }
  130. if (result.tips.length) {
  131. result.tips.forEach((tip) => this.warn({
  132. id: query.filename,
  133. message: tip,
  134. }));
  135. }
  136. return {
  137. code: result.code,
  138. map: normalizeSourceMap(result.map, id),
  139. };
  140. }
  141. else if (query.type === 'style') {
  142. debug(`transform(${id})`);
  143. const block = descriptor.styles[query.index];
  144. let preprocessOptions = options.preprocessOptions || {};
  145. const preprocessLang = (options.preprocessStyles
  146. ? block.lang
  147. : undefined);
  148. if (preprocessLang) {
  149. preprocessOptions =
  150. preprocessOptions[preprocessLang] || preprocessOptions;
  151. // include node_modules for imports by default
  152. switch (preprocessLang) {
  153. case 'scss':
  154. case 'sass':
  155. preprocessOptions = {
  156. includePaths: ['node_modules'],
  157. ...preprocessOptions,
  158. };
  159. break;
  160. case 'less':
  161. case 'stylus':
  162. preprocessOptions = {
  163. paths: ['node_modules'],
  164. ...preprocessOptions,
  165. };
  166. }
  167. }
  168. else {
  169. preprocessOptions = {};
  170. }
  171. const result = await compiler_sfc_1.compileStyleAsync({
  172. filename: query.filename,
  173. id: `data-v-${query.id}`,
  174. source: code,
  175. scoped: block.scoped,
  176. vars: !!block.vars,
  177. modules: !!block.module,
  178. postcssOptions: options.postcssOptions,
  179. postcssPlugins: options.postcssPlugins,
  180. modulesOptions: options.cssModulesOptions,
  181. preprocessLang,
  182. preprocessCustomRequire: options.preprocessCustomRequire,
  183. preprocessOptions,
  184. });
  185. if (result.errors.length) {
  186. result.errors.forEach((error) => this.error({
  187. id: query.filename,
  188. message: error.message,
  189. }));
  190. return null;
  191. }
  192. if (query.module) {
  193. return {
  194. code: `export default ${_(result.modules)}`,
  195. map: null,
  196. };
  197. }
  198. else {
  199. return {
  200. code: result.code,
  201. map: normalizeSourceMap(result.map, id),
  202. };
  203. }
  204. }
  205. return null;
  206. }
  207. else if (filter(id)) {
  208. debug(`transform(${id})`);
  209. const { descriptor, errors } = parseSFC(code, id, rootContext);
  210. if (errors.length) {
  211. errors.forEach((error) => this.error(createRollupError(id, error)));
  212. return null;
  213. }
  214. // module id for scoped CSS & hot-reload
  215. const output = transformVueSFC(code, id, descriptor, { rootContext, isProduction, isServer, filterCustomBlock }, options);
  216. debug('transient .vue file:', '\n' + output + '\n');
  217. return {
  218. code: output,
  219. map: {
  220. mappings: '',
  221. },
  222. };
  223. }
  224. else {
  225. return null;
  226. }
  227. },
  228. };
  229. }
  230. exports.default = PluginVue;
  231. function createCustomBlockFilter(queries) {
  232. if (!queries || queries.length === 0)
  233. return () => false;
  234. const allowed = new Set(queries.filter((query) => /^[a-z]/i.test(query)));
  235. const disallowed = new Set(queries
  236. .filter((query) => /^![a-z]/i.test(query))
  237. .map((query) => query.substr(1)));
  238. const allowAll = queries.includes('*') || !queries.includes('!*');
  239. return (type) => {
  240. if (allowed.has(type))
  241. return true;
  242. if (disallowed.has(type))
  243. return true;
  244. return allowAll;
  245. };
  246. }
  247. function parseVuePartRequest(id) {
  248. const [filename, query] = id.split('?', 2);
  249. if (!query)
  250. return { vue: false, filename };
  251. const raw = querystring_1.default.parse(query);
  252. if ('vue' in raw) {
  253. return {
  254. ...raw,
  255. filename,
  256. vue: true,
  257. index: Number(raw.index),
  258. src: 'src' in raw,
  259. scoped: 'scoped' in raw,
  260. };
  261. }
  262. return { vue: false, filename };
  263. }
  264. const cache = new Map();
  265. function getDescriptor(id) {
  266. if (cache.has(id)) {
  267. return cache.get(id);
  268. }
  269. throw new Error(`${id} is not parsed yet`);
  270. }
  271. function parseSFC(code, id, sourceRoot) {
  272. const { descriptor, errors } = compiler_sfc_1.parse(code, {
  273. sourceMap: true,
  274. filename: id,
  275. sourceRoot: sourceRoot,
  276. });
  277. cache.set(id, descriptor);
  278. return { descriptor, errors: errors };
  279. }
  280. function transformVueSFC(code, resourcePath, descriptor, { rootContext, isProduction, isServer, filterCustomBlock, }, options) {
  281. const shortFilePath = path_1.relative(rootContext, resourcePath)
  282. .replace(/^(\.\.[\/\\])+/, '')
  283. .replace(/\\/g, '/');
  284. const id = hash_sum_1.default(isProduction ? shortFilePath + '\n' + code : shortFilePath);
  285. // feature information
  286. const hasScoped = descriptor.styles.some((s) => s.scoped);
  287. const templateImport = !descriptor.template
  288. ? ''
  289. : getTemplateCode(descriptor, resourcePath, id, hasScoped, isServer);
  290. const renderReplace = !descriptor.template
  291. ? ''
  292. : isServer
  293. ? `script.ssrRender = ssrRender`
  294. : `script.render = render`;
  295. const scriptImport = getScriptCode(descriptor, resourcePath);
  296. const stylesCode = getStyleCode(descriptor, resourcePath, id, options.preprocessStyles);
  297. const customBlocksCode = getCustomBlock(descriptor, resourcePath, filterCustomBlock);
  298. const output = [
  299. scriptImport,
  300. templateImport,
  301. stylesCode,
  302. customBlocksCode,
  303. renderReplace,
  304. ];
  305. if (hasScoped) {
  306. output.push(`script.__scopeId = ${_(`data-v-${id}`)}`);
  307. }
  308. if (!isProduction) {
  309. output.push(`script.__file = ${_(shortFilePath)}`);
  310. }
  311. else if (options.exposeFilename) {
  312. output.push(`script.__file = ${_(path_1.basename(shortFilePath))}`);
  313. }
  314. output.push('export default script');
  315. return output.join('\n');
  316. }
  317. function getTemplateCode(descriptor, resourcePath, id, hasScoped, isServer) {
  318. const renderFnName = isServer ? 'ssrRender' : 'render';
  319. let templateImport = `const ${renderFnName} = () => {}`;
  320. let templateRequest;
  321. if (descriptor.template) {
  322. const src = descriptor.template.src || resourcePath;
  323. const idQuery = `&id=${id}`;
  324. const scopedQuery = hasScoped ? `&scoped=true` : ``;
  325. const srcQuery = descriptor.template.src ? `&src` : ``;
  326. const attrsQuery = attrsToQuery(descriptor.template.attrs, 'js', true);
  327. const query = `?vue&type=template${idQuery}${srcQuery}${scopedQuery}${attrsQuery}`;
  328. templateRequest = _(src + query);
  329. templateImport = `import { ${renderFnName} } from ${templateRequest}`;
  330. }
  331. return templateImport;
  332. }
  333. function getScriptCode(descriptor, resourcePath) {
  334. let scriptImport = `const script = {}`;
  335. if (descriptor.script || descriptor.scriptSetup) {
  336. if (compiler_sfc_1.compileScript) {
  337. descriptor.script = compiler_sfc_1.compileScript(descriptor);
  338. }
  339. if (descriptor.script) {
  340. const src = descriptor.script.src || resourcePath;
  341. const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js');
  342. const srcQuery = descriptor.script.src ? `&src` : ``;
  343. const query = `?vue&type=script${srcQuery}${attrsQuery}`;
  344. const scriptRequest = _(src + query);
  345. scriptImport =
  346. `import script from ${scriptRequest}\n` +
  347. `export * from ${scriptRequest}`; // support named exports
  348. }
  349. }
  350. return scriptImport;
  351. }
  352. function getStyleCode(descriptor, resourcePath, id, preprocessStyles) {
  353. let stylesCode = ``;
  354. let hasCSSModules = false;
  355. if (descriptor.styles.length) {
  356. descriptor.styles.forEach((style, i) => {
  357. const src = style.src || resourcePath;
  358. // do not include module in default query, since we use it to indicate
  359. // that the module needs to export the modules json
  360. const attrsQuery = attrsToQuery(style.attrs, 'css', preprocessStyles);
  361. const attrsQueryWithoutModule = attrsQuery.replace(/&module(=true|=[^&]+)?/, '');
  362. // make sure to only pass id when necessary so that we don't inject
  363. // duplicate tags when multiple components import the same css file
  364. const idQuery = style.scoped ? `&id=${id}` : ``;
  365. const srcQuery = style.src ? `&src` : ``;
  366. const query = `?vue&type=style&index=${i}${srcQuery}${idQuery}`;
  367. const styleRequest = src + query + attrsQuery;
  368. const styleRequestWithoutModule = src + query + attrsQueryWithoutModule;
  369. if (style.module) {
  370. if (!hasCSSModules) {
  371. stylesCode += `\nconst cssModules = script.__cssModules = {}`;
  372. hasCSSModules = true;
  373. }
  374. stylesCode += genCSSModulesCode(id, i, styleRequest, styleRequestWithoutModule, style.module);
  375. }
  376. else {
  377. stylesCode += `\nimport ${_(styleRequest)}`;
  378. }
  379. // TODO SSR critical CSS collection
  380. });
  381. }
  382. return stylesCode;
  383. }
  384. function getCustomBlock(descriptor, resourcePath, filter) {
  385. let code = '';
  386. descriptor.customBlocks.forEach((block, index) => {
  387. if (filter(block.type)) {
  388. const src = block.src || resourcePath;
  389. const attrsQuery = attrsToQuery(block.attrs, block.type);
  390. const srcQuery = block.src ? `&src` : ``;
  391. const query = `?vue&type=${block.type}&index=${index}${srcQuery}${attrsQuery}`;
  392. const request = _(src + query);
  393. code += `import block${index} from ${request}\n`;
  394. code += `if (typeof block${index} === 'function') block${index}(script)\n`;
  395. }
  396. });
  397. return code;
  398. }
  399. function createRollupError(id, error) {
  400. if ('code' in error) {
  401. return {
  402. id,
  403. plugin: 'vue',
  404. pluginCode: String(error.code),
  405. message: error.message,
  406. frame: error.loc.source,
  407. parserError: error,
  408. loc: error.loc
  409. ? {
  410. file: id,
  411. line: error.loc.start.line,
  412. column: error.loc.start.column,
  413. }
  414. : undefined,
  415. };
  416. }
  417. else {
  418. return {
  419. id,
  420. plugin: 'vue',
  421. message: error.message,
  422. parserError: error,
  423. };
  424. }
  425. }
  426. // these are built-in query parameters so should be ignored
  427. // if the user happen to add them as attrs
  428. const ignoreList = ['id', 'index', 'src', 'type', 'lang'];
  429. function attrsToQuery(attrs, langFallback, forceLangFallback = false) {
  430. let query = ``;
  431. for (const name in attrs) {
  432. const value = attrs[name];
  433. if (!ignoreList.includes(name)) {
  434. query += `&${querystring_1.default.escape(name)}${value ? `=${querystring_1.default.escape(String(value))}` : ``}`;
  435. }
  436. }
  437. if (langFallback || attrs.lang) {
  438. query +=
  439. `lang` in attrs
  440. ? forceLangFallback
  441. ? `&lang.${langFallback}`
  442. : `&lang.${attrs.lang}`
  443. : `&lang.${langFallback}`;
  444. }
  445. return query;
  446. }
  447. function _(any) {
  448. return JSON.stringify(any);
  449. }
  450. function normalizeSourceMap(map, id) {
  451. if (!map)
  452. return null;
  453. if (!id.includes('type=script')) {
  454. map.file = id;
  455. map.sources[0] = id;
  456. }
  457. return {
  458. ...map,
  459. version: Number(map.version),
  460. mappings: typeof map.mappings === 'string' ? map.mappings : '',
  461. };
  462. }
  463. function genCSSModulesCode(
  464. // @ts-ignore
  465. id, index, request, requestWithoutModule, moduleName) {
  466. const styleVar = `style${index}`;
  467. let code =
  468. // first import the CSS for extraction
  469. `\nimport ${_(requestWithoutModule)}` +
  470. // then import the json file to expose to component...
  471. `\nimport ${styleVar} from ${_(request + '.js')}`;
  472. // inject variable
  473. const name = typeof moduleName === 'string' ? moduleName : '$style';
  474. code += `\ncssModules["${name}"] = ${styleVar}`;
  475. return code;
  476. }
  477. // overwrite for cjs require('rollup-plugin-vue')() usage
  478. module.exports = PluginVue;