buildPluginHtml.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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. exports.createBuildHtmlPlugin = void 0;
  7. const path_1 = __importDefault(require("path"));
  8. const fs_extra_1 = __importDefault(require("fs-extra"));
  9. const magic_string_1 = __importDefault(require("magic-string"));
  10. const utils_1 = require("../utils");
  11. const buildPluginAsset_1 = require("./buildPluginAsset");
  12. exports.createBuildHtmlPlugin = async (root, indexPath, publicBasePath, assetsDir, inlineLimit, resolver, shouldPreload, config) => {
  13. if (!fs_extra_1.default.existsSync(indexPath)) {
  14. return {
  15. renderIndex: () => '',
  16. htmlPlugin: null
  17. };
  18. }
  19. const rawHtml = await fs_extra_1.default.readFile(indexPath, 'utf-8');
  20. const preprocessedHtml = await utils_1.transformIndexHtml(rawHtml, config.indexHtmlTransforms, 'pre', true);
  21. const assets = new Map();
  22. let { html: processedHtml, js } = await compileHtml(root, preprocessedHtml, publicBasePath, assetsDir, inlineLimit, resolver, assets);
  23. const htmlPlugin = {
  24. name: 'vite:html',
  25. async load(id) {
  26. if (id === indexPath) {
  27. return js;
  28. }
  29. },
  30. generateBundle(_options, bundle) {
  31. buildPluginAsset_1.registerAssets(assets, bundle);
  32. }
  33. };
  34. const injectCSS = (html, filename) => {
  35. const tag = `<link rel="stylesheet" href="${publicBasePath}${path_1.default.posix.join(assetsDir, filename)}">`;
  36. if (/<\/head>/.test(html)) {
  37. return html.replace(/<\/head>/, `${tag}\n</head>`);
  38. }
  39. else {
  40. return tag + '\n' + html;
  41. }
  42. };
  43. const injectScript = (html, filename) => {
  44. filename = utils_1.isExternalUrl(filename)
  45. ? filename
  46. : `${publicBasePath}${path_1.default.posix.join(assetsDir, filename)}`;
  47. const tag = `<script type="module" src="${filename}"></script>`;
  48. if (/<\/head>/.test(html)) {
  49. return html.replace(/<\/head>/, `${tag}\n</head>`);
  50. }
  51. else {
  52. return html + '\n' + tag;
  53. }
  54. };
  55. const injectPreload = (html, filename) => {
  56. filename = utils_1.isExternalUrl(filename)
  57. ? filename
  58. : `${publicBasePath}${path_1.default.posix.join(assetsDir, filename)}`;
  59. const tag = `<link rel="modulepreload" href="${filename}" />`;
  60. if (/<\/head>/.test(html)) {
  61. return html.replace(/<\/head>/, `${tag}\n</head>`);
  62. }
  63. else {
  64. return tag + '\n' + html;
  65. }
  66. };
  67. const renderIndex = async (bundleOutput) => {
  68. let result = processedHtml;
  69. for (const chunk of bundleOutput) {
  70. if (chunk.type === 'chunk') {
  71. if (chunk.isEntry) {
  72. // js entry chunk
  73. result = injectScript(result, chunk.fileName);
  74. }
  75. else if (shouldPreload && shouldPreload(chunk)) {
  76. // async preloaded chunk
  77. result = injectPreload(result, chunk.fileName);
  78. }
  79. }
  80. else {
  81. // imported css chunks
  82. if (chunk.fileName.endsWith('.css') &&
  83. chunk.source &&
  84. !assets.has(chunk.fileName)) {
  85. result = injectCSS(result, chunk.fileName);
  86. }
  87. }
  88. }
  89. return await utils_1.transformIndexHtml(result, config.indexHtmlTransforms, 'post', true);
  90. };
  91. return {
  92. renderIndex,
  93. htmlPlugin
  94. };
  95. };
  96. // this extends the config in @vue/compiler-sfc with <link href>
  97. const assetAttrsConfig = {
  98. link: ['href'],
  99. video: ['src', 'poster'],
  100. source: ['src'],
  101. img: ['src'],
  102. image: ['xlink:href', 'href'],
  103. use: ['xlink:href', 'href']
  104. };
  105. // compile index.html to a JS module, importing referenced assets
  106. // and scripts
  107. const compileHtml = async (root, html, publicBasePath, assetsDir, inlineLimit, resolver, assets) => {
  108. const { parse, transform } = require('@vue/compiler-dom');
  109. // @vue/compiler-core doesn't like lowercase doctypes
  110. html = html.replace(/<!doctype\s/i, '<!DOCTYPE ');
  111. const ast = parse(html);
  112. let js = '';
  113. const s = new magic_string_1.default(html);
  114. const assetUrls = [];
  115. const viteHtmlTransform = (node) => {
  116. if (node.type === 1 /* ELEMENT */) {
  117. if (node.tag === 'script') {
  118. let shouldRemove = false;
  119. const srcAttr = node.props.find((p) => p.type === 6 /* ATTRIBUTE */ && p.name === 'src');
  120. const typeAttr = node.props.find((p) => p.type === 6 /* ATTRIBUTE */ && p.name === 'type');
  121. const isJsModule = typeAttr && typeAttr.value && typeAttr.value.content === 'module';
  122. if (isJsModule) {
  123. if (srcAttr && srcAttr.value) {
  124. if (!utils_1.isExternalUrl(srcAttr.value.content)) {
  125. // <script type="module" src="..."/>
  126. // add it as an import
  127. js += `\nimport ${JSON.stringify(srcAttr.value.content)}`;
  128. shouldRemove = true;
  129. }
  130. }
  131. else if (node.children.length) {
  132. // <script type="module">...</script>
  133. // add its content
  134. // TODO: if there are multiple inline module scripts on the page,
  135. // they should technically be turned into separate modules, but
  136. // it's hard to imagine any reason for anyone to do that.
  137. js += `\n` + node.children[0].content.trim() + `\n`;
  138. shouldRemove = true;
  139. }
  140. }
  141. if (shouldRemove) {
  142. // remove the script tag from the html. we are going to inject new
  143. // ones in the end.
  144. s.remove(node.loc.start.offset, node.loc.end.offset);
  145. }
  146. }
  147. // For asset references in index.html, also generate an import
  148. // statement for each - this will be handled by the asset plugin
  149. const assetAttrs = assetAttrsConfig[node.tag];
  150. if (assetAttrs) {
  151. for (const p of node.props) {
  152. if (p.type === 6 /* ATTRIBUTE */ &&
  153. p.value &&
  154. assetAttrs.includes(p.name) &&
  155. !utils_1.isExternalUrl(p.value.content) &&
  156. !utils_1.isDataUrl(p.value.content)) {
  157. assetUrls.push(p);
  158. }
  159. }
  160. }
  161. }
  162. };
  163. transform(ast, {
  164. nodeTransforms: [viteHtmlTransform]
  165. });
  166. // for each encountered asset url, rewrite original html so that it
  167. // references the post-build location.
  168. for (const attr of assetUrls) {
  169. const value = attr.value;
  170. const { fileName, content, url } = await buildPluginAsset_1.resolveAsset(resolver.requestToFile(value.content), root, publicBasePath, assetsDir, utils_1.cleanUrl(value.content).endsWith('.css') ? 0 : inlineLimit);
  171. s.overwrite(value.loc.start.offset, value.loc.end.offset, `"${url}"`);
  172. if (fileName && content) {
  173. assets.set(fileName, content);
  174. }
  175. }
  176. return {
  177. html: s.toString(),
  178. js
  179. };
  180. };
  181. //# sourceMappingURL=buildPluginHtml.js.map