123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- /**
- * 参考 https://github.com/jspm/vite-plugin-jspm,调整为需要的功能
- */
- import type { GeneratorOptions } from '@jspm/generator';
- import type { Plugin } from 'vite';
- import { Generator } from '@jspm/generator';
- import { load } from 'cheerio';
- import { minify } from 'html-minifier-terser';
- const DEFAULT_PROVIDER = 'jspm.io';
- type pluginOptions = GeneratorOptions & {
- debug?: boolean;
- defaultProvider?: 'esm.sh' | 'jsdelivr' | 'jspm.io';
- importmap?: Array<{ name: string; range?: string }>;
- };
- // async function getLatestVersionOfShims() {
- // const result = await fetch('https://ga.jspm.io/npm:es-module-shims');
- // const version = result.text();
- // return version;
- // }
- async function getShimsUrl(provide: string) {
- // const version = await getLatestVersionOfShims();
- const version = '1.10.0';
- const shimsSubpath = `dist/es-module-shims.js`;
- const providerShimsMap: Record<string, string> = {
- 'esm.sh': `https://esm.sh/es-module-shims@${version}/${shimsSubpath}`,
- // unpkg: `https://unpkg.com/es-module-shims@${version}/${shimsSubpath}`,
- jsdelivr: `https://cdn.jsdelivr.net/npm/es-module-shims@${version}/${shimsSubpath}`,
- // 下面两个CDN不稳定,暂时不用
- 'jspm.io': `https://ga.jspm.io/npm:es-module-shims@${version}/${shimsSubpath}`,
- };
- return providerShimsMap[provide] || providerShimsMap[DEFAULT_PROVIDER];
- }
- let generator: Generator;
- async function viteImportMapPlugin(
- pluginOptions?: pluginOptions,
- ): Promise<Plugin[]> {
- const { importmap } = pluginOptions || {};
- let isSSR = false;
- let isBuild = false;
- let installed = false;
- let installError: Error | null = null;
- const options: pluginOptions = Object.assign(
- {},
- {
- debug: false,
- defaultProvider: 'jspm.io',
- env: ['production', 'browser', 'module'],
- importmap: [],
- },
- pluginOptions,
- );
- generator = new Generator({
- ...options,
- baseUrl: process.cwd(),
- });
- if (options?.debug) {
- (async () => {
- for await (const { message, type } of generator.logStream()) {
- console.log(`${type}: ${message}`);
- }
- })();
- }
- const imports = options.inputMap?.imports ?? {};
- const scopes = options.inputMap?.scopes ?? {};
- const firstLayerKeys = Object.keys(scopes);
- const inputMapScopes: string[] = [];
- firstLayerKeys.forEach((key) => {
- inputMapScopes.push(...Object.keys(scopes[key] || {}));
- });
- const inputMapImports = Object.keys(imports);
- const allDepNames: string[] = [
- ...(importmap?.map((item) => item.name) || []),
- ...inputMapImports,
- ...inputMapScopes,
- ];
- const depNames = new Set<string>(allDepNames);
- const installDeps = importmap?.map((item) => ({
- range: item.range,
- target: item.name,
- }));
- return [
- {
- async config(_, { command, isSsrBuild }) {
- isBuild = command === 'build';
- isSSR = !!isSsrBuild;
- },
- enforce: 'pre',
- name: 'importmap:external',
- resolveId(id) {
- if (isSSR || !isBuild) {
- return null;
- }
- if (!depNames.has(id)) {
- return null;
- }
- return { external: true, id };
- },
- },
- {
- enforce: 'post',
- name: 'importmap:install',
- async resolveId() {
- if (isSSR || !isBuild || installed) {
- return null;
- }
- try {
- installed = true;
- await Promise.allSettled(
- (installDeps || []).map((dep) => generator.install(dep)),
- );
- } catch (error: any) {
- installError = error;
- installed = false;
- }
- return null;
- },
- },
- {
- buildEnd() {
- // 未生成importmap时,抛出错误,防止被turbo缓存
- if (!installed && !isSSR) {
- installError && console.error(installError);
- throw new Error('Importmap installation failed.');
- }
- },
- enforce: 'post',
- name: 'importmap:html',
- transformIndexHtml: {
- async handler(html) {
- if (isSSR || !isBuild) {
- return html;
- }
- const importmapJson = generator.getMap();
- if (!importmapJson) {
- return html;
- }
- const esModuleShimsSrc = await getShimsUrl(
- options.defaultProvider || DEFAULT_PROVIDER,
- );
- const resultHtml = await injectShimsToHtml(
- html,
- esModuleShimsSrc || '',
- );
- html = await minify(resultHtml || html, {
- collapseWhitespace: true,
- minifyCSS: true,
- minifyJS: true,
- removeComments: false,
- });
- return {
- html,
- tags: [
- {
- attrs: {
- type: 'importmap',
- },
- injectTo: 'head-prepend',
- tag: 'script',
- children: `${JSON.stringify(importmapJson)}`,
- },
- ],
- };
- },
- order: 'post',
- },
- },
- ];
- }
- async function injectShimsToHtml(html: string, esModuleShimUrl: string) {
- const $ = load(html);
- const $script = $(`script[type='module']`);
- if (!$script) {
- return;
- }
- const entry = $script.attr('src');
- $script.removeAttr('type');
- $script.removeAttr('crossorigin');
- $script.removeAttr('src');
- $script.html(`
- if (!HTMLScriptElement.supports || !HTMLScriptElement.supports('importmap')) {
- self.importShim = function () {
- const promise = new Promise((resolve, reject) => {
- document.head.appendChild(
- Object.assign(document.createElement('script'), {
- src: '${esModuleShimUrl}',
- crossorigin: 'anonymous',
- async: true,
- onload() {
- if (!importShim.$proxy) {
- resolve(importShim);
- } else {
- reject(new Error('No globalThis.importShim found:' + esModuleShimUrl));
- }
- },
- onerror(error) {
- reject(error);
- },
- }),
- );
- });
- importShim.$proxy = true;
- return promise.then((importShim) => importShim(...arguments));
- };
- }
- var modules = ['${entry}'];
- typeof importShim === 'function'
- ? modules.forEach((moduleName) => importShim(moduleName))
- : modules.forEach((moduleName) => import(moduleName));
- `);
- $('body').after($script);
- $('head').remove(`script[type='module']`);
- return $.html();
- }
- export { viteImportMapPlugin };
|