UNPKG

svelte-standalone

Version:

Transform Svelte components in standalone scripts!

155 lines (154 loc) 5.93 kB
import { build, defineConfig, loadConfigFromFile } from 'vite'; import fs from 'fs'; import tailwindcss from '@tailwindcss/vite'; import path from 'path'; import { visualizer } from 'rollup-plugin-visualizer'; import resolve from '@rollup/plugin-node-resolve'; import { svelte } from '@sveltejs/vite-plugin-svelte'; import cssnanoPlugin from 'cssnano'; import { purgeCSSPlugin } from '@fullhuman/postcss-purgecss'; import { libInjectCss } from 'vite-plugin-lib-inject-css'; import strip from '@rollup/plugin-strip'; import terser from '@rollup/plugin-terser'; import { rootDir } from '../../dir.js'; import { getPath } from '../utils/getPath.js'; import { pathToFileURL } from 'url'; import { includesTailwind } from '../utils/isDependency.js'; const tailwind = includesTailwind(); const svelteConfig = path.resolve(rootDir, 'svelte.config.js'); const svelteAliases = fs.existsSync(svelteConfig) ? (await import(pathToFileURL(svelteConfig).href)).default?.kit?.alias : undefined; const parseAlias = (alias) => { if (!alias) return undefined; return Object.fromEntries(Object.entries(alias).map(([key, value]) => { const newKey = key.replace('/*', ''); // Remove '/*' from the key const newValue = path.resolve(rootDir, value.replace('/*', '')); // Resolve the path return [newKey, newValue]; })); }; const normalizeComponentName = (componentName) => componentName.replace(/^[+$]/, ''); const isRuntime = (componentName) => componentName === 'runtime' || componentName === '$runtime' || componentName === '+runtime'; const getContent = (purgeDir, componentName, hasRuntime) => { const content = [path.resolve(rootDir, `${purgeDir}/**/*.{svelte,ts,js,css}`)]; if (!hasRuntime || isRuntime(componentName)) { content.push(path.resolve(rootDir, './src/shared/**/*.{svelte,ts,js,css}')); } return content; }; const getPostCSSPlugins = (purgeDir, componentName, hasRuntime) => { const content = getContent(purgeDir, componentName, hasRuntime); const s = new RegExp(`s-${componentName}`); return tailwind ? undefined : [ purgeCSSPlugin({ content, extractors: [ { extractor: (c) => c.match(/[A-Za-z0-9-_:/\.]+/g) || [], extensions: ['svelte', 'js', 'ts', 'css'] } ], safelist: { standard: [s] } }), cssnanoPlugin() ]; }; const getProd = (prod) => prod ? [ strip({ functions: ['console.log', 'console.warn', 'console.error', 'assert.*'] }), terser({ compress: { drop_console: true, unused: true, reduce_vars: true, pure_funcs: ['console.debug', 'debug'] }, output: { comments: false } }) ] : []; const commonPlugins = (componentName, visualizerDir) => [ svelte({ configFile: svelteConfig, compilerOptions: { cssHash: ({ name }) => `s-${name?.toLowerCase()}` } }), visualizer({ filename: `${visualizerDir}.status.html`, title: `${componentName} status` }), libInjectCss(), tailwind && tailwindcss() ]; const handleBuild = (files, prod, hasRuntime, viteConfig) => { return files.map((file) => { const rawComponentName = path.dirname(file).split(path.sep).at(-1) || ''; const componentName = normalizeComponentName(rawComponentName); const visualizerDir = path .dirname(file) .replace('src', 'static') .replace('_standalone', `dist${path.sep}visualizer`); const purgeDir = path.dirname(file).replace('embed.ts', ''); if (!componentName) { console.error('Invalid fileName: ', file); return; } return defineConfig({ css: { postcss: { plugins: getPostCSSPlugins(purgeDir, rawComponentName, hasRuntime) } }, plugins: commonPlugins(componentName, visualizerDir), build: { minify: prod, emptyOutDir: false, lib: { formats: ['umd'], entry: file, name: componentName, fileName: componentName }, outDir: path.resolve(rootDir, 'static/dist/standalone'), rollupOptions: { output: { chunkFileNames: 'chunks/[name].[hash].js', assetFileNames: 'assets/[name][extname]', entryFileNames: `${componentName}.min.js` }, plugins: [resolve({ browser: true, dedupe: ['svelte'] }), ...getProd(prod)] } }, resolve: { alias: { ...parseAlias(svelteAliases), ...parseAlias(viteConfig?.resolve?.alias) } }, mode: viteConfig?.mode, envPrefix: viteConfig?.envPrefix, define: viteConfig?.define, envDir: viteConfig?.envDir }); }); }; export const buildStandalone = async (files, prod, hasRuntime, mode) => { const viteConfig = await loadConfigFromFile({ command: 'build', mode: mode ?? 'production' }, getPath('vite.config'), rootDir).then((result) => result?.config); try { const configs = handleBuild(files, prod, hasRuntime, viteConfig); await Promise.all(configs.map((c) => build({ ...c, configFile: false, mode }))); } catch (handleBuildError) { console.error('Error during handleBuild:', handleBuildError); } };