Skip to content

Commit

Permalink
feat: Simple CSS injection support (#483)
Browse files Browse the repository at this point in the history
Co-authored-by: EGOIST <0x142857@gmail.com>
  • Loading branch information
zhmushan and egoist committed Dec 8, 2021
1 parent 96d6493 commit 8fb5d26
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/cli-main.ts
Expand Up @@ -84,7 +84,7 @@ export async function main(options: Options = {}) {
})
.option('--loader <ext=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, {
Expand Down
2 changes: 1 addition & 1 deletion src/esbuild/index.ts
Expand Up @@ -109,7 +109,7 @@ export async function runEsbuild(
}),
options.tsconfigDecoratorMetadata && swcPlugin({ logger }),
nativeNodeModulesPlugin(),
postcssPlugin({ css }),
postcssPlugin({ css, inject: options.injectStyle }),
sveltePlugin({ css }),
...(options.esbuildPlugins || []),
]
Expand Down
102 changes: 80 additions & 22 deletions 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<string, string>
inject?: boolean
}): Plugin => {
return {
name: 'postcss',
Expand All @@ -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
Expand All @@ -34,45 +36,101 @@ 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')
}

// 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',
}
})
Expand Down
6 changes: 3 additions & 3 deletions src/options.ts
Expand Up @@ -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
}
15 changes: 15 additions & 0 deletions test/index.test.ts
Expand Up @@ -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}'))
})

0 comments on commit 8fb5d26

Please sign in to comment.