esbuild-plugin-vue-iii
Version:
a esbuild plugin for vue 3 SFC files.
298 lines • 13.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transformMain = void 0;
const querystring_1 = __importDefault(require("querystring"));
const path_1 = __importDefault(require("path"));
const compiler_sfc_1 = require("@vue/compiler-sfc");
const descriptorCache_1 = require("./utils/descriptorCache");
// import { normalizePath } from '@rollup/pluginutils'
const path_2 = require("path");
const script_1 = require("./script");
const template_1 = require("./template");
// import { isOnlyTemplateChanged, isEqualBlock } from './handleHotUpdate'
const source_map_1 = require("source-map");
const error_1 = require("./utils/error");
const isEqualBlock = () => { };
const isOnlyTemplateChanged = () => { };
const normalizePath = (filename) => filename.split(path_2.win32.sep).join(path_2.posix.sep);
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async function transformMain(code, filename, options, pluginContext, ssr, asCustomElement) {
var _a;
const { root, devServer, isProduction } = options;
// prev descriptor is only set and used for hmr
const prevDescriptor = descriptorCache_1.getPrevDescriptor(filename);
const { descriptor, errors } = descriptorCache_1.createDescriptor(filename, code, root, isProduction);
if (errors.length) {
errors.forEach((error) => pluginContext.error(error_1.createRollupError(filename, error)));
return null;
}
// feature information
const hasScoped = descriptor.styles.some((s) => s.scoped);
// script
const { code: scriptCode, map } = await genScriptCode(descriptor, options, pluginContext, ssr);
// template
// Check if we can use compile template as inlined render function
// inside <script setup>. This can only be done for build because
// inlined template cannot be individually hot updated.
const useInlineTemplate = !devServer &&
descriptor.scriptSetup &&
!(descriptor.template && descriptor.template.src);
const hasTemplateImport = descriptor.template && !useInlineTemplate;
let templateCode = '';
let templateMap;
if (hasTemplateImport) {
;
({ code: templateCode, map: templateMap } = await genTemplateCode(descriptor, options, pluginContext, ssr));
}
let renderReplace = '';
if (hasTemplateImport) {
renderReplace = ssr
? `_sfc_main.ssrRender = _sfc_ssrRender`
: `_sfc_main.render = _sfc_render`;
}
else {
// #2128
// User may empty the template but we didn't provide rerender function before
if (prevDescriptor &&
!isEqualBlock(descriptor.template, prevDescriptor.template)) {
renderReplace = ssr
? `_sfc_main.ssrRender = () => {}`
: `_sfc_main.render = () => {}`;
}
}
// styles
const stylesCode = await genStyleCode(descriptor, pluginContext, asCustomElement);
// custom blocks
const customBlocksCode = await genCustomBlockCode(descriptor, pluginContext);
const output = [
scriptCode,
templateCode,
stylesCode,
customBlocksCode,
renderReplace
];
if (hasScoped) {
output.push(`_sfc_main.__scopeId = ${JSON.stringify(`data-v-${descriptor.id}`)}`);
}
if (devServer && !isProduction) {
// expose filename during serve for devtools to pickup
output.push(`_sfc_main.__file = ${JSON.stringify(filename)}`);
}
// HMR
if (devServer &&
devServer.config.server.hmr !== false &&
!ssr &&
!isProduction) {
output.push(`_sfc_main.__hmrId = ${JSON.stringify(descriptor.id)}`);
output.push(`typeof __VUE_HMR_RUNTIME__ !== 'undefined' && ` +
`__VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)`);
// check if the template is the only thing that changed
if (prevDescriptor && isOnlyTemplateChanged(prevDescriptor, descriptor)) {
output.push(`export const _rerender_only = true`);
}
output.push(`import.meta.hot.accept(({ default: updated, _rerender_only }) => {`, ` if (_rerender_only) {`, ` __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)`, ` } else {`, ` __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)`, ` }`, `})`);
}
// SSR module registration by wrapping user setup
if (ssr) {
const normalizedFilename = normalizePath(path_1.default.relative(options.root, filename));
output.push(`import { useSSRContext as __vite_useSSRContext } from 'vue'`, `const _sfc_setup = _sfc_main.setup`, `_sfc_main.setup = (props, ctx) => {`, ` const ssrContext = __vite_useSSRContext()`, ` ;(ssrContext.modules || (ssrContext.modules = new Set())).add(${JSON.stringify(normalizedFilename)})`, ` return _sfc_setup ? _sfc_setup(props, ctx) : undefined`, `}`);
}
// if the template is inlined into the main module (indicated by the presence
// of templateMap, we need to concatenate the two source maps.
let resolvedMap = map;
if (map && templateMap) {
const generator = source_map_1.SourceMapGenerator.fromSourceMap(new source_map_1.SourceMapConsumer(map));
const offset = ((_a = scriptCode.match(/\r?\n/g)) === null || _a === void 0 ? void 0 : _a.length) || 1;
const templateMapConsumer = new source_map_1.SourceMapConsumer(templateMap);
templateMapConsumer.eachMapping((m) => {
generator.addMapping({
source: m.source,
original: { line: m.originalLine, column: m.originalColumn },
generated: {
line: m.generatedLine + offset,
column: m.generatedColumn
}
});
});
resolvedMap = generator.toJSON();
// if this is a template only update, we will be reusing a cached version
// of the main module compile result, which has outdated sourcesContent.
resolvedMap.sourcesContent = templateMap.sourcesContent;
}
output.push(`export default _sfc_main`);
return {
code: output.join('\n'),
map: resolvedMap || {
mappings: ''
}
};
}
exports.transformMain = transformMain;
async function genTemplateCode(descriptor, options, pluginContext, ssr) {
const template = descriptor.template;
// If the template is not using pre-processor AND is not using external src,
// compile and inline it directly in the main module. When served in vite this
// saves an extra request per SFC which can improve load performance.
if (!template.lang && !template.src) {
return template_1.transformTemplateInMain(template.content, descriptor, options, pluginContext, ssr);
}
else {
if (template.src) {
await linkSrcToDescriptor(template.src, descriptor, pluginContext);
}
const src = template.src || descriptor.filename;
const srcQuery = template.src ? `&src` : ``;
const attrsQuery = attrsToQuery(template.attrs, 'js', true);
const query = `?vue&type=template${srcQuery}${attrsQuery}`;
const request = JSON.stringify(src + query);
const renderFnName = ssr ? 'ssrRender' : 'render';
return {
code: `import { ${renderFnName} as _sfc_${renderFnName} } from ${request}`,
map: undefined
};
}
}
async function genScriptCode(descriptor, options, pluginContext, ssr) {
let scriptCode = `const _sfc_main = {}`;
let map;
const script = script_1.resolveScript(descriptor, options, ssr);
if (script) {
// If the script is js/ts and has no external src, it can be directly placed
// in the main module.
if ((!script.lang || (script.lang === 'ts' && options.devServer)) &&
!script.src) {
scriptCode = script.content;
map = script.map;
if (script.lang === 'ts') {
const result = await options.devServer.transformWithEsbuild(scriptCode, descriptor.filename, { loader: 'ts' }, map);
scriptCode = result.code;
map = result.map;
}
scriptCode = compiler_sfc_1.rewriteDefault(scriptCode, `_sfc_main`);
}
else {
if (script.src) {
await linkSrcToDescriptor(script.src, descriptor, pluginContext);
}
const src = script.src || descriptor.filename;
const langFallback = (script.src && path_1.default.extname(src).slice(1)) || 'js';
const attrsQuery = attrsToQuery(script.attrs, langFallback);
const srcQuery = script.src ? `&src` : ``;
const query = `?vue&type=script${srcQuery}${attrsQuery}`;
const request = JSON.stringify(src + query);
scriptCode =
`import _sfc_main from ${request}\n` + `export * from ${request}`; // support named exports
}
}
return {
code: scriptCode,
map: map
};
}
async function genStyleCode(descriptor, pluginContext, asCustomElement) {
let stylesCode = ``;
let hasCSSModules = false;
if (descriptor.styles.length) {
for (let i = 0; i < descriptor.styles.length; i++) {
const style = descriptor.styles[i];
if (style.src) {
await linkSrcToDescriptor(style.src, descriptor, pluginContext);
}
const src = style.src || descriptor.filename;
// do not include module in default query, since we use it to indicate
// that the module needs to export the modules json
const attrsQuery = attrsToQuery(style.attrs, 'css');
const srcQuery = style.src ? `&src` : ``;
const directQuery = asCustomElement ? `&inline` : ``;
const query = `?vue&type=style&index=${i}${srcQuery}${directQuery}`;
const styleRequest = src + query + attrsQuery;
if (style.module) {
if (asCustomElement) {
throw new Error(`<style module> is not supported in custom elements mode.`);
}
if (!hasCSSModules) {
stylesCode += `\nconst cssModules = _sfc_main.__cssModules = {}`;
hasCSSModules = true;
}
stylesCode += genCSSModulesCode(i, styleRequest, style.module);
}
else {
if (asCustomElement) {
stylesCode += `\nimport _style_${i} from ${JSON.stringify(styleRequest)}`;
}
else {
stylesCode += `\nimport ${JSON.stringify(styleRequest)}`;
}
}
// TODO SSR critical CSS collection
}
if (asCustomElement) {
stylesCode += `\n_sfc_main.styles = [${descriptor.styles
.map((_, i) => `_style_${i}`)
.join(',')}]`;
}
}
return stylesCode;
}
function genCSSModulesCode(index, request, moduleName) {
const styleVar = `style${index}`;
const exposedName = typeof moduleName === 'string' ? moduleName : '$style';
// inject `.module` before extension so vite handles it as css module
const moduleRequest = request.replace(/\.(\w+)$/, '.module.$1');
return (`\nimport ${styleVar} from ${JSON.stringify(moduleRequest)}` +
`\ncssModules["${exposedName}"] = ${styleVar}`);
}
async function genCustomBlockCode(descriptor, pluginContext) {
let code = '';
for (let index = 0; index < descriptor.customBlocks.length; index++) {
const block = descriptor.customBlocks[index];
if (block.src) {
await linkSrcToDescriptor(block.src, descriptor, pluginContext);
}
const src = block.src || descriptor.filename;
const attrsQuery = attrsToQuery(block.attrs, block.type);
const srcQuery = block.src ? `&src` : ``;
const query = `?vue&type=${block.type}&index=${index}${srcQuery}${attrsQuery}`;
const request = JSON.stringify(src + query);
code += `import block${index} from ${request}\n`;
code += `if (typeof block${index} === 'function') block${index}(_sfc_main)\n`;
}
return code;
}
/**
* For blocks with src imports, it is important to link the imported file
* with its owner SFC descriptor so that we can get the information about
* the owner SFC when compiling that file in the transform phase.
*/
async function linkSrcToDescriptor(src, descriptor, pluginContext) {
var _a;
const srcFile = ((_a = (await pluginContext.resolve(src, descriptor.filename))) === null || _a === void 0 ? void 0 : _a.id) || src;
// #1812 if the src points to a dep file, the resolved id may contain a
// version query.
descriptorCache_1.setDescriptor(srcFile.replace(/\?.*$/, ''), descriptor);
}
// these are built-in query parameters so should be ignored
// if the user happen to add them as attrs
const ignoreList = ['id', 'index', 'src', 'type', 'lang', 'module'];
function attrsToQuery(attrs, langFallback, forceLangFallback = false) {
let query = ``;
for (const name in attrs) {
const value = attrs[name];
if (!ignoreList.includes(name)) {
query += `&${querystring_1.default.escape(name)}${value ? `=${querystring_1.default.escape(String(value))}` : ``}`;
}
}
if (langFallback || attrs.lang) {
query +=
`lang` in attrs
? forceLangFallback
? `&lang.${langFallback}`
: `&lang.${attrs.lang}`
: `&lang.${langFallback}`;
}
return query;
}
//# sourceMappingURL=main.js.map