diff --git a/src/cli-main.ts b/src/cli-main.ts index d9a0e17e..06876bf6 100644 --- a/src/cli-main.ts +++ b/src/cli-main.ts @@ -84,7 +84,7 @@ export async function main(options: Options = {}) { }) .option('--loader ', 'Specify the loader for a file extension') .option('--no-config', 'Disable config file') - .option('--no-shims', 'Disable cjs and esm shims') + .option('--inject-style', 'Inject style tag to document head') .action(async (files: string[], flags) => { const { build } = await import('.') Object.assign(options, { diff --git a/src/esbuild/index.ts b/src/esbuild/index.ts index eb5aba70..7ec68586 100644 --- a/src/esbuild/index.ts +++ b/src/esbuild/index.ts @@ -109,7 +109,7 @@ export async function runEsbuild( }), options.tsconfigDecoratorMetadata && swcPlugin({ logger }), nativeNodeModulesPlugin(), - postcssPlugin({ css }), + postcssPlugin({ css, inject: options.injectStyle }), sveltePlugin({ css }), ...(options.esbuildPlugins || []), ] diff --git a/src/esbuild/postcss.ts b/src/esbuild/postcss.ts index 2f07cf14..54a23d75 100644 --- a/src/esbuild/postcss.ts +++ b/src/esbuild/postcss.ts @@ -1,12 +1,14 @@ import fs from 'fs' import path from 'path' -import { Plugin } from 'esbuild' +import { Plugin, transform } from 'esbuild' import { getPostcss } from '../utils' export const postcssPlugin = ({ css, + inject, }: { css?: Map + inject?: boolean }): Plugin => { return { name: 'postcss', @@ -25,7 +27,7 @@ export const postcssPlugin = ({ const result = await loadConfig({}, path.dirname(file)) configCache.set(file, result) return result - } catch (error) { + } catch (error: any) { if (error.message.includes('No PostCSS Config found in')) { const result = { plugins: [], options: {} } return result @@ -34,11 +36,50 @@ export const postcssPlugin = ({ } } + build.onResolve({ filter: /^#style-inject$/ }, () => { + return { path: '#style-inject', namespace: '#style-inject' } + }) + + build.onLoad( + { filter: /^#style-inject$/, namespace: '#style-inject' }, + () => { + return { + // Taken from https://github.com/egoist/style-inject/blob/master/src/index.js (MIT) + contents: ` + export default function styleInject(css, { insertAt } = {}) { + if (!css || typeof document === 'undefined') return + + const head = document.head || document.getElementsByTagName('head')[0] + const style = document.createElement('style') + style.type = 'text/css' + + if (insertAt === 'top') { + if (head.firstChild) { + head.insertBefore(style, head.firstChild) + } else { + head.appendChild(style) + } + } else { + head.appendChild(style) + } + + if (style.styleSheet) { + style.styleSheet.cssText = css + } else { + style.appendChild(document.createTextNode(css)) + } + } + `, + loader: 'js', + } + } + ) + build.onLoad({ filter: /\.css$/ }, async (args) => { let contents: string if (css && args.path.endsWith('.svelte.css')) { - contents = css.get(args.path) as string + contents = css.get(args.path)! } else { contents = await fs.promises.readFile(args.path, 'utf8') } @@ -46,33 +87,50 @@ export const postcssPlugin = ({ // 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', + if (plugins || plugins.length > 0) { + // Load postcss + const postcss = getPostcss() + if (!postcss) { + return { + errors: [ + { + text: `postcss is not installed`, + }, + ], + } } + + // Transform CSS + const result = await postcss + ?.default(plugins) + .process(contents, { ...options, from: args.path }) + + contents = result.css } - // Load postcss - const postcss = getPostcss() - if (!postcss) { + if (inject) { + contents = ( + await transform(contents, { + minify: build.initialOptions.minify, + minifyIdentifiers: build.initialOptions.minifyIdentifiers, + minifySyntax: build.initialOptions.minifySyntax, + minifyWhitespace: build.initialOptions.minifyWhitespace, + loader: 'css', + }) + ).code + + contents = `import styleInject from '#style-inject';styleInject(${JSON.stringify( + contents + )})` + return { - errors: [ - { - text: `postcss is not installed`, - }, - ], + contents, + loader: 'js', } } - // Transform CSS - const result = await postcss - ?.default(plugins) - .process(contents, { ...options, from: args.path }) - return { - contents: result.css, + contents, loader: 'css', } }) diff --git a/src/options.ts b/src/options.ts index 5a7cb9f5..7af195c9 100644 --- a/src/options.ts +++ b/src/options.ts @@ -123,8 +123,8 @@ export type Options = { */ tsconfig?: string /** - * CJS and ESM shims - * @default `true` + * Inject CSS as style tags to document head + * @default {false} */ - shims?: boolean + injectStyle?: boolean } diff --git a/test/index.test.ts b/test/index.test.ts index 3e30cc85..98deeabd 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -697,3 +697,18 @@ test('decorator metadata', async (t) => { const contents = await getFileContent('dist/input.js') t.assert(contents.includes(`Reflect.metadata("design:type"`)) }) + +test('inject style', async (t) => { + const { outFiles, output } = await run( + t.title, + { + 'input.ts': `import './style.css'`, + 'style.css': `.hello { color: red }` + }, + { + flags: ['--inject-style', '--minify'], + } + ) + t.deepEqual(outFiles, ['input.js']) + t.assert(output.includes('.hello{color:red}')) +})