index.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.VueLoaderPlugin = void 0;
  4. const path = require("path");
  5. const qs = require("querystring");
  6. const loaderUtils = require("loader-utils");
  7. const hash = require("hash-sum");
  8. const compiler_1 = require("./compiler");
  9. const select_1 = require("./select");
  10. const hotReload_1 = require("./hotReload");
  11. const cssModules_1 = require("./cssModules");
  12. const formatError_1 = require("./formatError");
  13. const plugin_1 = require("./plugin");
  14. exports.VueLoaderPlugin = plugin_1.default;
  15. const resolveScript_1 = require("./resolveScript");
  16. const descriptorCache_1 = require("./descriptorCache");
  17. let errorEmitted = false;
  18. const exportHelperPath = JSON.stringify(require.resolve('./exportHelper'));
  19. function loader(source) {
  20. var _a;
  21. const loaderContext = this;
  22. // check if plugin is installed
  23. if (!errorEmitted &&
  24. !loaderContext['thread-loader'] &&
  25. !loaderContext[plugin_1.default.NS]) {
  26. loaderContext.emitError(new Error(`vue-loader was used without the corresponding plugin. ` +
  27. `Make sure to include VueLoaderPlugin in your webpack config.`));
  28. errorEmitted = true;
  29. }
  30. const stringifyRequest = (r) => loaderUtils.stringifyRequest(loaderContext, r);
  31. const { mode, target, sourceMap, rootContext, resourcePath, resourceQuery = '', } = loaderContext;
  32. const rawQuery = resourceQuery.slice(1);
  33. const incomingQuery = qs.parse(rawQuery);
  34. const options = (loaderUtils.getOptions(loaderContext) ||
  35. {});
  36. const isServer = (_a = options.isServerBuild) !== null && _a !== void 0 ? _a : target === 'node';
  37. const isProduction = mode === 'production' || process.env.NODE_ENV === 'production';
  38. const filename = resourcePath.replace(/\?.*$/, '');
  39. const { descriptor, errors } = compiler_1.compiler.parse(source, {
  40. filename,
  41. sourceMap,
  42. });
  43. const asCustomElement = typeof options.customElement === 'boolean'
  44. ? options.customElement
  45. : (options.customElement || /\.ce\.vue$/).test(filename);
  46. // cache descriptor
  47. (0, descriptorCache_1.setDescriptor)(filename, descriptor);
  48. if (errors.length) {
  49. errors.forEach((err) => {
  50. (0, formatError_1.formatError)(err, source, resourcePath);
  51. loaderContext.emitError(err);
  52. });
  53. return ``;
  54. }
  55. // module id for scoped CSS & hot-reload
  56. const rawShortFilePath = path
  57. .relative(rootContext || process.cwd(), filename)
  58. .replace(/^(\.\.[\/\\])+/, '');
  59. const shortFilePath = rawShortFilePath.replace(/\\/g, '/');
  60. const id = hash(isProduction
  61. ? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
  62. : shortFilePath);
  63. // if the query has a type field, this is a language block request
  64. // e.g. foo.vue?type=template&id=xxxxx
  65. // and we will return early
  66. if (incomingQuery.type) {
  67. return (0, select_1.selectBlock)(descriptor, id, options, loaderContext, incomingQuery, !!options.appendExtension);
  68. }
  69. // feature information
  70. const hasScoped = descriptor.styles.some((s) => s.scoped);
  71. const needsHotReload = !isServer &&
  72. !isProduction &&
  73. !!(descriptor.script || descriptor.scriptSetup || descriptor.template) &&
  74. options.hotReload !== false;
  75. // extra properties to attach to the script object
  76. // we need to do this in a tree-shaking friendly manner
  77. const propsToAttach = [];
  78. // script
  79. let scriptImport = `const script = {}`;
  80. let isTS = false;
  81. const { script, scriptSetup } = descriptor;
  82. if (script || scriptSetup) {
  83. const lang = (script === null || script === void 0 ? void 0 : script.lang) || (scriptSetup === null || scriptSetup === void 0 ? void 0 : scriptSetup.lang);
  84. isTS = !!(lang && /tsx?/.test(lang));
  85. const src = (script && !scriptSetup && script.src) || resourcePath;
  86. const attrsQuery = attrsToQuery((scriptSetup || script).attrs, 'js');
  87. const query = `?vue&type=script${attrsQuery}${resourceQuery}`;
  88. const scriptRequest = stringifyRequest(src + query);
  89. scriptImport =
  90. `import script from ${scriptRequest}\n` +
  91. // support named exports
  92. `export * from ${scriptRequest}`;
  93. }
  94. // template
  95. let templateImport = ``;
  96. let templateRequest;
  97. const renderFnName = isServer ? `ssrRender` : `render`;
  98. const useInlineTemplate = (0, resolveScript_1.canInlineTemplate)(descriptor, isProduction);
  99. if (descriptor.template && !useInlineTemplate) {
  100. const src = descriptor.template.src || resourcePath;
  101. const idQuery = `&id=${id}`;
  102. const scopedQuery = hasScoped ? `&scoped=true` : ``;
  103. const attrsQuery = attrsToQuery(descriptor.template.attrs);
  104. const tsQuery = options.enableTsInTemplate !== false && isTS ? `&ts=true` : ``;
  105. const query = `?vue&type=template${idQuery}${scopedQuery}${tsQuery}${attrsQuery}${resourceQuery}`;
  106. templateRequest = stringifyRequest(src + query);
  107. templateImport = `import { ${renderFnName} } from ${templateRequest}`;
  108. propsToAttach.push([renderFnName, renderFnName]);
  109. }
  110. // styles
  111. let stylesCode = ``;
  112. let hasCSSModules = false;
  113. const nonWhitespaceRE = /\S+/;
  114. if (descriptor.styles.length) {
  115. descriptor.styles
  116. .filter((style) => style.src || nonWhitespaceRE.test(style.content))
  117. .forEach((style, i) => {
  118. const src = style.src || resourcePath;
  119. const attrsQuery = attrsToQuery(style.attrs, 'css');
  120. // make sure to only pass id when necessary so that we don't inject
  121. // duplicate tags when multiple components import the same css file
  122. const idQuery = !style.src || style.scoped ? `&id=${id}` : ``;
  123. const inlineQuery = asCustomElement ? `&inline` : ``;
  124. const query = `?vue&type=style&index=${i}${idQuery}${inlineQuery}${attrsQuery}${resourceQuery}`;
  125. const styleRequest = stringifyRequest(src + query);
  126. if (style.module) {
  127. if (asCustomElement) {
  128. loaderContext.emitError(`<style module> is not supported in custom element mode.`);
  129. }
  130. if (!hasCSSModules) {
  131. stylesCode += `\nconst cssModules = {}`;
  132. propsToAttach.push([`__cssModules`, `cssModules`]);
  133. hasCSSModules = true;
  134. }
  135. stylesCode += (0, cssModules_1.genCSSModulesCode)(id, i, styleRequest, style.module, needsHotReload);
  136. }
  137. else {
  138. if (asCustomElement) {
  139. stylesCode += `\nimport _style_${i} from ${styleRequest}`;
  140. }
  141. else {
  142. stylesCode += `\nimport ${styleRequest}`;
  143. }
  144. }
  145. // TODO SSR critical CSS collection
  146. });
  147. if (asCustomElement) {
  148. propsToAttach.push([
  149. `styles`,
  150. `[${descriptor.styles.map((_, i) => `_style_${i}`)}]`,
  151. ]);
  152. }
  153. }
  154. let code = [templateImport, scriptImport, stylesCode]
  155. .filter(Boolean)
  156. .join('\n');
  157. // attach scope Id for runtime use
  158. if (hasScoped) {
  159. propsToAttach.push([`__scopeId`, `"data-v-${id}"`]);
  160. }
  161. // Expose filename. This is used by the devtools and Vue runtime warnings.
  162. if (!isProduction) {
  163. // Expose the file's full path in development, so that it can be opened
  164. // from the devtools.
  165. propsToAttach.push([
  166. `__file`,
  167. JSON.stringify(rawShortFilePath.replace(/\\/g, '/')),
  168. ]);
  169. }
  170. else if (options.exposeFilename) {
  171. // Libraries can opt-in to expose their components' filenames in production builds.
  172. // For security reasons, only expose the file's basename in production.
  173. propsToAttach.push([`__file`, JSON.stringify(path.basename(resourcePath))]);
  174. }
  175. // custom blocks
  176. if (descriptor.customBlocks && descriptor.customBlocks.length) {
  177. code += `\n/* custom blocks */\n`;
  178. code +=
  179. descriptor.customBlocks
  180. .map((block, i) => {
  181. const src = block.attrs.src || resourcePath;
  182. const attrsQuery = attrsToQuery(block.attrs);
  183. const blockTypeQuery = `&blockType=${qs.escape(block.type)}`;
  184. const issuerQuery = block.attrs.src
  185. ? `&issuerPath=${qs.escape(resourcePath)}`
  186. : '';
  187. const query = `?vue&type=custom&index=${i}${blockTypeQuery}${issuerQuery}${attrsQuery}${resourceQuery}`;
  188. return (`import block${i} from ${stringifyRequest(src + query)}\n` +
  189. `if (typeof block${i} === 'function') block${i}(script)`);
  190. })
  191. .join(`\n`) + `\n`;
  192. }
  193. // finalize
  194. if (!propsToAttach.length) {
  195. code += `\n\nconst __exports__ = script;`;
  196. }
  197. else {
  198. code += `\n\nimport exportComponent from ${exportHelperPath}`;
  199. code += `\nconst __exports__ = /*#__PURE__*/exportComponent(script, [${propsToAttach
  200. .map(([key, val]) => `['${key}',${val}]`)
  201. .join(',')}])`;
  202. }
  203. if (needsHotReload) {
  204. code += (0, hotReload_1.genHotReloadCode)(id, templateRequest);
  205. }
  206. code += `\n\nexport default __exports__`;
  207. return code;
  208. }
  209. exports.default = loader;
  210. // these are built-in query parameters so should be ignored
  211. // if the user happen to add them as attrs
  212. const ignoreList = ['id', 'index', 'src', 'type'];
  213. function attrsToQuery(attrs, langFallback) {
  214. let query = ``;
  215. for (const name in attrs) {
  216. const value = attrs[name];
  217. if (!ignoreList.includes(name)) {
  218. query += `&${qs.escape(name)}=${value ? qs.escape(String(value)) : ``}`;
  219. }
  220. }
  221. if (langFallback && !(`lang` in attrs)) {
  222. query += `&lang=${langFallback}`;
  223. }
  224. return query;
  225. }