mirror of
https://github.com/egoist/tsup.git
synced 2025-12-08 20:35:58 +00:00
feat: add svelte support
This commit is contained in:
parent
40a38dda88
commit
e18e1c8a39
@ -54,6 +54,7 @@
|
||||
"ts-essentials": "^7.0.1",
|
||||
"ts-jest": "^26.5.1",
|
||||
"tsup": "^4.2.0",
|
||||
"typescript": "^4.1.5"
|
||||
"typescript": "^4.1.5",
|
||||
"svelte": "3.34.0"
|
||||
}
|
||||
}
|
||||
|
||||
38
src/index.ts
38
src/index.ts
@ -17,6 +17,7 @@ import glob from 'globby'
|
||||
import { PrettyError } from './errors'
|
||||
import { postcssPlugin } from './plugins/postcss'
|
||||
import { externalPlugin } from './plugins/external'
|
||||
import { sveltePlugin } from './plugins/svelte'
|
||||
|
||||
const textDecoder = new TextDecoder('utf-8')
|
||||
|
||||
@ -75,7 +76,10 @@ export type Options = {
|
||||
splitting?: boolean
|
||||
}
|
||||
|
||||
export type NormalizedOptions = MarkRequired<Options, 'entryPoints' | 'format'>
|
||||
export type NormalizedOptions = MarkRequired<
|
||||
Options,
|
||||
'entryPoints' | 'format' | 'outDir'
|
||||
>
|
||||
|
||||
const services: Map<string, Service> = new Map()
|
||||
|
||||
@ -103,8 +107,8 @@ const getOutputExtensionMap = (
|
||||
}
|
||||
|
||||
export async function runEsbuild(
|
||||
options: Options,
|
||||
{ format }: { format: Format }
|
||||
options: NormalizedOptions,
|
||||
{ format, css }: { format: Format; css?: Set<string> }
|
||||
): Promise<BuildResult | undefined> {
|
||||
let service = services.get(format)
|
||||
if (!service) {
|
||||
@ -114,7 +118,7 @@ export async function runEsbuild(
|
||||
const pkg = await loadPkg(process.cwd())
|
||||
const deps = await getDeps(process.cwd())
|
||||
const external = [...deps, ...(options.external || [])]
|
||||
const outDir = options.outDir || 'dist'
|
||||
const outDir = options.outDir
|
||||
|
||||
const outExtension = getOutputExtensionMap(pkg.type, format)
|
||||
const env: { [k: string]: string } = {
|
||||
@ -149,7 +153,8 @@ export async function runEsbuild(
|
||||
// esbuild's `external` option doesn't support RegExp
|
||||
// So here we use a custom plugin to implement it
|
||||
externalPlugin(external),
|
||||
postcssPlugin,
|
||||
postcssPlugin(),
|
||||
sveltePlugin({ css }),
|
||||
],
|
||||
define: {
|
||||
...options.define,
|
||||
@ -286,6 +291,8 @@ const normalizeOptions = async (
|
||||
)
|
||||
}
|
||||
|
||||
options.outDir = options.outDir || 'dist'
|
||||
|
||||
// Build in cjs format by default
|
||||
if (!options.format) {
|
||||
options.format = ['cjs']
|
||||
@ -346,10 +353,25 @@ export async function build(_options: Options) {
|
||||
})
|
||||
}
|
||||
|
||||
const buildAll = () =>
|
||||
Promise.all([
|
||||
...options.format.map((format) => runEsbuild(options, { format })),
|
||||
const buildAll = async () => {
|
||||
const css: Set<string> = new Set()
|
||||
await Promise.all([
|
||||
...options.format.map((format, index) =>
|
||||
runEsbuild(options, { format, css: index === 0 ? css : undefined })
|
||||
),
|
||||
])
|
||||
if (css.size > 0) {
|
||||
let cssCode = ''
|
||||
for (const cssPart of css) {
|
||||
cssCode += cssPart
|
||||
}
|
||||
await fs.promises.writeFile(
|
||||
join(options.outDir, 'styles.css'),
|
||||
cssCode,
|
||||
'utf8'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(makeLabel('CLI', 'info'), `Target: ${options.target}`)
|
||||
|
||||
@ -3,67 +3,69 @@ import path from 'path'
|
||||
import { Plugin } from 'esbuild'
|
||||
import { getPostcss } from '../utils'
|
||||
|
||||
export const postcssPlugin: Plugin = {
|
||||
name: 'postcss',
|
||||
export const postcssPlugin = (): Plugin => {
|
||||
return {
|
||||
name: 'postcss',
|
||||
|
||||
setup(build) {
|
||||
const configCache = new Map()
|
||||
setup(build) {
|
||||
const configCache = new Map()
|
||||
|
||||
const getPostcssConfig = async (file: string) => {
|
||||
const loadConfig = require('postcss-load-config')
|
||||
const getPostcssConfig = async (file: string) => {
|
||||
const loadConfig = require('postcss-load-config')
|
||||
|
||||
if (configCache.has(file)) {
|
||||
return configCache.get(file)
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await loadConfig({}, path.dirname(file))
|
||||
configCache.set(file, result)
|
||||
return result
|
||||
} catch (error) {
|
||||
if (error.message.includes('No PostCSS Config found in')) {
|
||||
const result = { plugins: [], options: {} }
|
||||
return result
|
||||
if (configCache.has(file)) {
|
||||
return configCache.get(file)
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await loadConfig({}, path.dirname(file))
|
||||
configCache.set(file, result)
|
||||
return result
|
||||
} catch (error) {
|
||||
if (error.message.includes('No PostCSS Config found in')) {
|
||||
const result = { plugins: [], options: {} }
|
||||
return result
|
||||
}
|
||||
throw error
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
build.onLoad({ filter: /\.css$/ }, async (args) => {
|
||||
const contents = await fs.promises.readFile(args.path, 'utf8')
|
||||
build.onLoad({ filter: /\.css$/ }, async (args) => {
|
||||
const contents = await fs.promises.readFile(args.path, 'utf8')
|
||||
|
||||
// Load postcss config
|
||||
const { plugins, options } = await getPostcssConfig(args.path)
|
||||
// Load postcss config
|
||||
const { plugins, options } = await getPostcssConfig(args.path)
|
||||
|
||||
// Return if no postcss plugins are supplied
|
||||
if (!plugins || plugins.length === 0) {
|
||||
return {
|
||||
contents,
|
||||
loader: 'css',
|
||||
}
|
||||
}
|
||||
|
||||
// Load postcss
|
||||
const postcss = getPostcss()
|
||||
if (!postcss) {
|
||||
return {
|
||||
errors: [
|
||||
{
|
||||
text: `postcss is not installed`,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
// Tranform CSS
|
||||
const result = await postcss
|
||||
?.default(plugins)
|
||||
.process(contents, { ...options, from: args.path })
|
||||
|
||||
// Return if no postcss plugins are supplied
|
||||
if (!plugins || plugins.length === 0) {
|
||||
return {
|
||||
contents,
|
||||
contents: result.css,
|
||||
loader: 'css',
|
||||
}
|
||||
}
|
||||
|
||||
// Load postcss
|
||||
const postcss = getPostcss()
|
||||
if (!postcss) {
|
||||
return {
|
||||
errors: [
|
||||
{
|
||||
text: `postcss is not installed`,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
// Tranform CSS
|
||||
const result = await postcss
|
||||
?.default(plugins)
|
||||
.process(contents, { ...options, from: args.path })
|
||||
|
||||
return {
|
||||
contents: result.css,
|
||||
loader: 'css',
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
61
src/plugins/svelte.ts
Normal file
61
src/plugins/svelte.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { Plugin } from 'esbuild'
|
||||
import { localRequire } from '../utils'
|
||||
|
||||
export const sveltePlugin = ({ css }: { css?: Set<string> }): Plugin => {
|
||||
return {
|
||||
name: 'svelte',
|
||||
|
||||
setup(build) {
|
||||
let svelte: typeof import('svelte/compiler')
|
||||
|
||||
build.onLoad({ filter: /\.svelte$/ }, async (args) => {
|
||||
svelte = svelte || localRequire('svelte/compiler')
|
||||
|
||||
if (!svelte) {
|
||||
return {
|
||||
errors: [{ text: `You need to install "svelte" in your project` }],
|
||||
}
|
||||
}
|
||||
|
||||
// This converts a message in Svelte's format to esbuild's format
|
||||
let convertMessage = ({ message, start, end }: any) => {
|
||||
let location
|
||||
if (start && end) {
|
||||
let lineText = source.split(/\r\n|\r|\n/g)[start.line - 1]
|
||||
let lineEnd = start.line === end.line ? end.column : lineText.length
|
||||
location = {
|
||||
file: filename,
|
||||
line: start.line,
|
||||
column: start.column,
|
||||
length: lineEnd - start.column,
|
||||
lineText,
|
||||
}
|
||||
}
|
||||
return { text: message, location }
|
||||
}
|
||||
|
||||
// Load the file from the file system
|
||||
let source = await fs.promises.readFile(args.path, 'utf8')
|
||||
let filename = path.relative(process.cwd(), args.path)
|
||||
|
||||
// Convert Svelte syntax to JavaScript
|
||||
try {
|
||||
const result = svelte.compile(source, {
|
||||
filename,
|
||||
css: false,
|
||||
})
|
||||
if (css && result.css) {
|
||||
css.add(result.css.code)
|
||||
}
|
||||
let contents =
|
||||
result.js.code + `//# sourceMappingURL=` + result.js.map.toUrl()
|
||||
return { contents, warnings: result.warnings.map(convertMessage) }
|
||||
} catch (e) {
|
||||
return { errors: [convertMessage(e)] }
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -119,6 +119,11 @@ export function getPostcss(): null | typeof import('postcss') {
|
||||
return p && require(p)
|
||||
}
|
||||
|
||||
export function localRequire(moduleName: string) {
|
||||
const p = resovleFrom.silent(process.cwd(), moduleName)
|
||||
return p && require(p)
|
||||
}
|
||||
|
||||
export function pathExists(p: string) {
|
||||
return new Promise((resolve) => {
|
||||
fs.access(p, (err) => {
|
||||
|
||||
@ -416,3 +416,78 @@ test('disable code splitting to get proper module.exports =', async () => {
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('bundle svelte', async () => {
|
||||
const { output, getFileContent } = await run(
|
||||
getTestName(),
|
||||
{
|
||||
'input.ts': `import App from './App.svelte'
|
||||
export { App }
|
||||
`,
|
||||
'App.svelte': `
|
||||
<script>
|
||||
let msg = 'hello svelte'
|
||||
</script>
|
||||
|
||||
<span>{msg}</span>
|
||||
|
||||
<style>
|
||||
span {color: red}
|
||||
</style>
|
||||
`,
|
||||
},
|
||||
{
|
||||
// To make the snapshot leaner
|
||||
flags: ['--external', 'svelte/internal'],
|
||||
}
|
||||
)
|
||||
expect(output).toMatchInlineSnapshot(`
|
||||
"\\"use strict\\";Object.defineProperty(exports, \\"__esModule\\", {value: true});// App.svelte
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var _internal = require('svelte/internal');
|
||||
function create_fragment(ctx) {
|
||||
let span;
|
||||
return {
|
||||
c() {
|
||||
span = _internal.element.call(void 0, \\"span\\");
|
||||
span.textContent = \`\${msg}\`;
|
||||
_internal.attr.call(void 0, span, \\"class\\", \\"svelte-1jo4k3z\\");
|
||||
},
|
||||
m(target, anchor) {
|
||||
_internal.insert.call(void 0, target, span, anchor);
|
||||
},
|
||||
p: _internal.noop,
|
||||
i: _internal.noop,
|
||||
o: _internal.noop,
|
||||
d(detaching) {
|
||||
if (detaching)
|
||||
_internal.detach.call(void 0, span);
|
||||
}
|
||||
};
|
||||
}
|
||||
var msg = \\"hello svelte\\";
|
||||
var App = class extends _internal.SvelteComponent {
|
||||
constructor(options) {
|
||||
super();
|
||||
_internal.init.call(void 0, this, options, null, create_fragment, _internal.safe_not_equal, {});
|
||||
}
|
||||
};
|
||||
var App_default = App;
|
||||
|
||||
|
||||
exports.App = App_default;
|
||||
"
|
||||
`)
|
||||
|
||||
expect(await getFileContent('dist/styles.css')).toMatchInlineSnapshot(
|
||||
`"span.svelte-1jo4k3z{color:red}"`
|
||||
)
|
||||
})
|
||||
|
||||
@ -3822,6 +3822,11 @@ supports-hyperlinks@^2.0.0:
|
||||
has-flag "^4.0.0"
|
||||
supports-color "^7.0.0"
|
||||
|
||||
svelte@3.34.0:
|
||||
version "3.34.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.34.0.tgz#a0195a0db0305d78df87520711317b99e6fb8a26"
|
||||
integrity sha512-xWcaQ/J4Yd5k0UWz+ef6i5RW5WP3hNpluEI2qtTTKlMOXERHpVL509O9lIw7sgEn1JjJgTOS+lnnDj99vQ3YqQ==
|
||||
|
||||
symbol-tree@^3.2.4:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user