478 lines
13 KiB
JavaScript
478 lines
13 KiB
JavaScript
// src/build/build.ts
|
|
import { colors, debug, fs as fs3, importFile, withSpinner } from "@vuepress/utils";
|
|
import { build as viteBuild } from "vite";
|
|
|
|
// src/resolveViteConfig.ts
|
|
import { mergeConfig } from "vite";
|
|
|
|
// src/plugins/vuepressMainPlugin.ts
|
|
import { fs, sanitizeFileName } from "@vuepress/utils";
|
|
import autoprefixer from "autoprefixer";
|
|
import history from "connect-history-api-fallback";
|
|
import postcssrc from "postcss-load-config";
|
|
var vuepressMainPlugin = ({
|
|
app,
|
|
isBuild,
|
|
isServer
|
|
}) => ({
|
|
name: "vuepress:main",
|
|
config: async () => {
|
|
if (!isBuild) {
|
|
await app.writeTemp(
|
|
"vite-root/index.html",
|
|
fs.readFileSync(app.options.templateDev).toString().replace(
|
|
/<\/body>/,
|
|
`<script type="module">
|
|
import 'vuepress/client-app'
|
|
</script>
|
|
</body>`
|
|
)
|
|
);
|
|
}
|
|
const clientPackages = [
|
|
// although discouraged, but users may still use `@vuepress/client` directly
|
|
"@vuepress/client",
|
|
"vuepress",
|
|
...app.pluginApi.plugins.filter(({ name }) => name !== "user-config").map(({ name }) => name)
|
|
];
|
|
let postcssPlugins;
|
|
try {
|
|
const postcssConfigResult = await postcssrc();
|
|
postcssPlugins = postcssConfigResult.plugins;
|
|
} catch (error) {
|
|
postcssPlugins = [autoprefixer];
|
|
}
|
|
return {
|
|
root: app.dir.temp("vite-root"),
|
|
base: app.options.base,
|
|
mode: !isBuild || app.env.isDebug ? "development" : "production",
|
|
define: await resolveDefine({ app, isBuild, isServer }),
|
|
publicDir: app.dir.public(),
|
|
cacheDir: app.dir.cache(),
|
|
resolve: {
|
|
alias: await resolveAlias({ app, isServer })
|
|
},
|
|
css: {
|
|
postcss: {
|
|
plugins: isServer ? [] : postcssPlugins
|
|
},
|
|
preprocessorOptions: {
|
|
scss: { charset: false }
|
|
}
|
|
},
|
|
server: {
|
|
host: app.options.host,
|
|
port: app.options.port,
|
|
open: app.options.open,
|
|
// ref: https://github.com/vitejs/vite/issues/15784#issuecomment-1923683823
|
|
fs: { cachedChecks: false }
|
|
},
|
|
build: {
|
|
ssr: isServer,
|
|
outDir: isServer ? app.dir.temp(".server") : app.dir.dest(),
|
|
emptyOutDir: false,
|
|
cssCodeSplit: false,
|
|
rollupOptions: {
|
|
input: app.dir.client(
|
|
fs.readJsonSync(app.dir.client("package.json")).exports["./app"]
|
|
),
|
|
output: {
|
|
sanitizeFileName,
|
|
...isServer ? {
|
|
// also add hash to ssr entry file, so that users could build multiple sites in a single process
|
|
entryFileNames: `[name].[hash].mjs`
|
|
} : {}
|
|
},
|
|
preserveEntrySignatures: "allow-extension"
|
|
},
|
|
minify: isServer ? false : !app.env.isDebug
|
|
},
|
|
optimizeDeps: {
|
|
exclude: clientPackages
|
|
},
|
|
ssr: {
|
|
format: "esm",
|
|
noExternal: clientPackages
|
|
}
|
|
};
|
|
},
|
|
generateBundle(_, bundle) {
|
|
if (isServer) {
|
|
Object.keys(bundle).forEach((key) => {
|
|
if (bundle[key].type === "asset") {
|
|
delete bundle[key];
|
|
}
|
|
});
|
|
}
|
|
},
|
|
configureServer(server) {
|
|
return () => {
|
|
server.middlewares.use(
|
|
history({
|
|
rewrites: [
|
|
{
|
|
from: /\.html$/,
|
|
to: "/index.html"
|
|
}
|
|
]
|
|
})
|
|
);
|
|
};
|
|
}
|
|
});
|
|
var resolveAlias = async ({
|
|
app,
|
|
isServer
|
|
}) => {
|
|
const alias = {
|
|
"@internal": app.dir.temp("internal"),
|
|
"@temp": app.dir.temp(),
|
|
"@source": app.dir.source()
|
|
};
|
|
const aliasResult = await app.pluginApi.hooks.alias.process(app, isServer);
|
|
aliasResult.forEach(
|
|
(aliasObject) => Object.entries(aliasObject).forEach(([key, value]) => {
|
|
alias[key] = value;
|
|
})
|
|
);
|
|
return [
|
|
...Object.keys(alias).map((item) => ({
|
|
find: item,
|
|
replacement: alias[item]
|
|
})),
|
|
...isServer ? [] : [
|
|
{
|
|
find: /^vue$/,
|
|
replacement: "vue/dist/vue.runtime.esm-bundler.js"
|
|
},
|
|
{
|
|
find: /^vue-router$/,
|
|
replacement: "vue-router/dist/vue-router.esm-bundler.js"
|
|
}
|
|
]
|
|
];
|
|
};
|
|
var resolveDefine = async ({
|
|
app,
|
|
isBuild,
|
|
isServer
|
|
}) => {
|
|
const define = {
|
|
__VUEPRESS_VERSION__: JSON.stringify(app.version),
|
|
__VUEPRESS_BASE__: JSON.stringify(app.options.base),
|
|
__VUEPRESS_DEV__: JSON.stringify(!isBuild),
|
|
__VUEPRESS_SSR__: JSON.stringify(isServer),
|
|
// @see http://link.vuejs.org/feature-flags
|
|
// enable options API by default
|
|
__VUE_OPTIONS_API__: JSON.stringify(true),
|
|
__VUE_PROD_DEVTOOLS__: JSON.stringify(app.env.isDebug),
|
|
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: JSON.stringify(app.env.isDebug)
|
|
};
|
|
if (app.env.isDebug) {
|
|
define["process.env.NODE_ENV"] = JSON.stringify("development");
|
|
}
|
|
const defineResult = await app.pluginApi.hooks.define.process(app, isServer);
|
|
defineResult.forEach(
|
|
(defineObject) => Object.entries(defineObject).forEach(([key, value]) => {
|
|
define[key] = JSON.stringify(value);
|
|
})
|
|
);
|
|
return define;
|
|
};
|
|
|
|
// src/plugins/vuepressUserConfigPlugin.ts
|
|
var vuepressUserConfigPlugin = (options) => ({
|
|
name: "vuepress:user-config",
|
|
config: () => options.viteOptions ?? {}
|
|
});
|
|
|
|
// src/plugins/vuepressVuePlugin.ts
|
|
import vuePlugin from "@vitejs/plugin-vue";
|
|
var vuepressVuePlugin = (options) => {
|
|
return vuePlugin({
|
|
...options.vuePluginOptions,
|
|
template: {
|
|
...options.vuePluginOptions?.template,
|
|
transformAssetUrls: resolveTransformAssetUrls(options)
|
|
}
|
|
});
|
|
};
|
|
var isAssetURLTagConfig = (transformAssetUrls) => Object.values(transformAssetUrls).some((val) => Array.isArray(val));
|
|
var resolveTransformAssetUrls = (options) => {
|
|
const defaultTransformAssetUrls = { includeAbsolute: true };
|
|
const { transformAssetUrls: userTransformAssetUrls } = options.vuePluginOptions?.template ?? {};
|
|
if (typeof userTransformAssetUrls !== "object") {
|
|
return defaultTransformAssetUrls;
|
|
}
|
|
if (isAssetURLTagConfig(userTransformAssetUrls)) {
|
|
return {
|
|
...defaultTransformAssetUrls,
|
|
tags: userTransformAssetUrls
|
|
};
|
|
}
|
|
return {
|
|
...defaultTransformAssetUrls,
|
|
...userTransformAssetUrls
|
|
};
|
|
};
|
|
|
|
// src/resolveViteConfig.ts
|
|
var resolveViteConfig = async ({
|
|
app,
|
|
options,
|
|
isBuild,
|
|
isServer
|
|
}) => {
|
|
return mergeConfig(
|
|
{
|
|
clearScreen: false,
|
|
configFile: false,
|
|
logLevel: !isBuild || app.env.isDebug ? "info" : "warn",
|
|
esbuild: {
|
|
charset: "utf8"
|
|
},
|
|
plugins: [
|
|
vuepressVuePlugin(options),
|
|
vuepressMainPlugin({ app, isBuild, isServer }),
|
|
vuepressUserConfigPlugin(options)
|
|
]
|
|
},
|
|
// some vite options would not take effect inside a plugin, so we still need to merge them here in addition to userConfigPlugin
|
|
options.viteOptions ?? {}
|
|
);
|
|
};
|
|
|
|
// src/build/renderPage.ts
|
|
import { fs as fs2, renderHead } from "@vuepress/utils";
|
|
import { ssrContextKey } from "vue";
|
|
|
|
// src/build/renderPagePrefetchLinks.ts
|
|
var renderPagePrefetchLinks = ({
|
|
app,
|
|
outputEntryChunk,
|
|
pageChunkFiles
|
|
}) => {
|
|
const shouldPrefetch = app.options.shouldPrefetch;
|
|
if (shouldPrefetch === false) {
|
|
return "";
|
|
}
|
|
const prefetchFiles = outputEntryChunk.dynamicImports.filter(
|
|
(item) => !pageChunkFiles.some((file) => file === item)
|
|
);
|
|
return prefetchFiles.map((item) => {
|
|
const type = item.endsWith(".js") ? "script" : item.endsWith(".css") ? "style" : "";
|
|
if (shouldPrefetch !== true && !shouldPrefetch(item, type)) {
|
|
return "";
|
|
}
|
|
return `<link rel="prefetch" href="${app.options.base}${item}" as="${type}">`;
|
|
}).join("");
|
|
};
|
|
|
|
// src/build/renderPagePreloadLinks.ts
|
|
var renderPagePreloadLinks = ({
|
|
app,
|
|
outputEntryChunk,
|
|
pageChunkFiles
|
|
}) => {
|
|
const shouldPreload = app.options.shouldPreload;
|
|
if (shouldPreload === false) {
|
|
return "";
|
|
}
|
|
const preloadFiles = Array.from(
|
|
/* @__PURE__ */ new Set([
|
|
outputEntryChunk.fileName,
|
|
...outputEntryChunk.imports,
|
|
...pageChunkFiles
|
|
])
|
|
);
|
|
return preloadFiles.map((item) => {
|
|
const type = item.endsWith(".js") ? "script" : item.endsWith(".css") ? "style" : "";
|
|
if (shouldPreload === true && type !== "script" && type !== "style") {
|
|
return "";
|
|
}
|
|
if (shouldPreload !== true && !shouldPreload(item, type)) {
|
|
return "";
|
|
}
|
|
if (type === "script") {
|
|
return `<link rel="modulepreload" href="${app.options.base}${item}">`;
|
|
}
|
|
return `<link rel="preload" href="${app.options.base}${item}"${type !== "" ? ` as="${type}"` : ""}>`;
|
|
}).join("");
|
|
};
|
|
|
|
// src/build/renderPageScripts.ts
|
|
var renderPageScripts = ({
|
|
app,
|
|
outputEntryChunk
|
|
}) => `<script type="module" src="${app.options.base}${outputEntryChunk.fileName}" defer></script>`;
|
|
|
|
// src/build/renderPageStyles.ts
|
|
var renderPageStyles = ({
|
|
app,
|
|
outputCssAsset
|
|
}) => outputCssAsset ? [
|
|
`<link rel="preload" href="${app.options.base}${outputCssAsset.fileName}" as="style">`,
|
|
`<link rel="stylesheet" href="${app.options.base}${outputCssAsset.fileName}">`
|
|
].join("") : "";
|
|
|
|
// src/build/resolvePageChunkFiles.ts
|
|
var resolvePageChunkFiles = ({
|
|
page,
|
|
output
|
|
}) => output.filter(
|
|
(item) => item.type === "chunk" && item.facadeModuleId === page.chunkFilePath
|
|
).flatMap(({ fileName, imports, dynamicImports }) => [
|
|
fileName,
|
|
...imports,
|
|
...dynamicImports
|
|
]);
|
|
|
|
// src/build/renderPage.ts
|
|
var renderPage = async ({
|
|
app,
|
|
page,
|
|
vueApp,
|
|
vueRouter,
|
|
renderToString,
|
|
ssrTemplate,
|
|
output,
|
|
outputEntryChunk,
|
|
outputCssAsset
|
|
}) => {
|
|
await vueRouter.push(page.path);
|
|
await vueRouter.isReady();
|
|
delete vueApp._context.provides[ssrContextKey];
|
|
const ssrContext = {
|
|
lang: "en",
|
|
head: []
|
|
};
|
|
const pageRendered = await renderToString(vueApp, ssrContext);
|
|
const pageChunkFiles = resolvePageChunkFiles({ page, output });
|
|
const html = await app.options.templateBuildRenderer(ssrTemplate, {
|
|
content: pageRendered,
|
|
head: ssrContext.head.map(renderHead).join(""),
|
|
lang: ssrContext.lang,
|
|
prefetch: renderPagePrefetchLinks({
|
|
app,
|
|
outputEntryChunk,
|
|
pageChunkFiles
|
|
}),
|
|
preload: renderPagePreloadLinks({
|
|
app,
|
|
outputEntryChunk,
|
|
pageChunkFiles
|
|
}),
|
|
scripts: renderPageScripts({ app, outputEntryChunk }),
|
|
styles: renderPageStyles({ app, outputCssAsset }),
|
|
version: app.version
|
|
});
|
|
await fs2.outputFile(page.htmlFilePath, html);
|
|
};
|
|
|
|
// src/build/build.ts
|
|
var log = debug("vuepress:bundler-vite/build");
|
|
var build = async (options, app) => {
|
|
await app.pluginApi.hooks.extendsBundlerOptions.process(options, app);
|
|
log("compiling start");
|
|
let clientOutput;
|
|
let serverOutput;
|
|
await withSpinner("Compiling with vite")(async () => {
|
|
const clientConfig = await resolveViteConfig({
|
|
app,
|
|
options,
|
|
isBuild: true,
|
|
isServer: false
|
|
});
|
|
const serverConfig = await resolveViteConfig({
|
|
app,
|
|
options,
|
|
isBuild: true,
|
|
isServer: true
|
|
});
|
|
[clientOutput, serverOutput] = await Promise.all([
|
|
viteBuild(clientConfig),
|
|
viteBuild(serverConfig)
|
|
]);
|
|
});
|
|
log("compiling finish");
|
|
await withSpinner(`Rendering ${app.pages.length} pages`)(async (spinner) => {
|
|
const clientEntryChunk = clientOutput.output.find(
|
|
(item) => item.type === "chunk" && item.isEntry
|
|
);
|
|
const clientCssAsset = clientOutput.output.find(
|
|
(item) => item.type === "asset" && item.fileName.endsWith(".css")
|
|
);
|
|
const serverEntryChunk = serverOutput.output.find(
|
|
(item) => item.type === "chunk" && item.isEntry
|
|
);
|
|
const serverEntryPath = app.dir.temp(".server", serverEntryChunk.fileName);
|
|
const { createVueApp } = await importFile(serverEntryPath);
|
|
const { app: vueApp, router: vueRouter } = await createVueApp();
|
|
const { renderToString } = await import("vue/server-renderer");
|
|
const ssrTemplate = await fs3.readFile(app.options.templateBuild, {
|
|
encoding: "utf8"
|
|
});
|
|
for (const page of app.pages) {
|
|
if (spinner) spinner.text = `Rendering pages ${colors.magenta(page.path)}`;
|
|
await renderPage({
|
|
app,
|
|
page,
|
|
vueApp,
|
|
vueRouter,
|
|
renderToString,
|
|
ssrTemplate,
|
|
output: clientOutput.output,
|
|
outputEntryChunk: clientEntryChunk,
|
|
outputCssAsset: clientCssAsset
|
|
});
|
|
}
|
|
});
|
|
if (!app.env.isDebug) {
|
|
await fs3.remove(app.dir.temp(".server"));
|
|
}
|
|
};
|
|
|
|
// src/dev.ts
|
|
import { createRequire } from "module";
|
|
import { colors as colors2, fs as fs4 } from "@vuepress/utils";
|
|
import { createServer } from "vite";
|
|
var require2 = createRequire(import.meta.url);
|
|
var dev = async (options, app) => {
|
|
await app.pluginApi.hooks.extendsBundlerOptions.process(options, app);
|
|
const viteConfig = await resolveViteConfig({
|
|
app,
|
|
options,
|
|
isBuild: false,
|
|
isServer: false
|
|
});
|
|
const server = await createServer(viteConfig);
|
|
await server.listen();
|
|
const viteVersion = fs4.readJsonSync(
|
|
require2.resolve("vite/package.json")
|
|
).version;
|
|
server.config.logger.info(
|
|
colors2.cyan(`
|
|
vite v${viteVersion}`) + colors2.green(` dev server running at:
|
|
`),
|
|
{
|
|
clear: !server.config.logger.hasWarned
|
|
}
|
|
);
|
|
server.printUrls();
|
|
return server.close.bind(server);
|
|
};
|
|
|
|
// src/viteBundler.ts
|
|
var viteBundler = (options = {}) => ({
|
|
name: "@vuepress/bundler-vite",
|
|
dev: (app) => dev(options, app),
|
|
build: (app) => build(options, app)
|
|
});
|
|
|
|
// src/index.ts
|
|
var src_default = viteBundler;
|
|
export {
|
|
src_default as default,
|
|
viteBundler
|
|
};
|