rollup-plugin-vue-postcss/rollup.config.js
2022-12-18 11:03:51 +01:00

240 lines
6.5 KiB
JavaScript

import html from '@rollup/plugin-html'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import replace from '@rollup/plugin-replace'
import fs from 'fs'
import { createFilter } from '@rollup/pluginutils'
import { compileScript, compileStyleAsync, compileTemplate, parse } from '@vue/compiler-sfc'
import postcss from 'postcss'
import url from 'postcss-url'
import path from 'path'
import cssnano from 'cssnano'
import terser from '@rollup/plugin-terser'
const DEFAULT_VARIABLE = '__script__'
function isProduction () {
return process.env.NODE_ENV === 'production'
}
function transformScript (descriptor, id) {
if (descriptor.script === null && descriptor.scriptSetup === null) {
return {
bindings: {},
code: 'export default {}',
map: null,
descriptor
}
}
const script = compileScript(descriptor, {
id,
isProd: isProduction(),
sourceMap: true,
inlineTemplate: isProduction()
})
return {
bindings: script.bindings,
code: script.content,
map: script.map,
descriptor
}
}
function transformOptions (code) {
const newCode = code
.replaceAll('__VUE_OPTIONS_API__', 'true')
.replaceAll('__VUE_PROD_DEVTOOLS__', 'false')
return {
code: newCode,
map: null
}
}
function vuePostcss (options = {}) {
const filter = createFilter(options.include, options.exclude)
const styles = []
const components = new Map()
const processor = postcss(options.postcssPlugins || [])
let cssFileName = options.cssFileName
return {
name: 'vue-postcss',
renderStart (outputOptions, inputOptions) {
if (cssFileName) {
return
}
if (inputOptions.input.length === 0) {
this.error('cssFileName should be specified or there should be at least one input file.')
}
cssFileName = path.basename(inputOptions.input[0], '.js') + '.css'
},
async load (id) {
const [path, rawQueryString] = id.split('?')
if (!path.endsWith('.vue') || rawQueryString === undefined) {
return null
}
const queryString = new URLSearchParams(rawQueryString)
const component = components.get(path)
let snippet = {}
switch (queryString.get('type')) {
case 'template':
snippet = compileTemplate({
source: component.descriptor.template.content,
id,
filename: id,
isProd: isProduction(),
inMap: component.descriptor.template.map,
compilerOptions: {
inline: false,
bindingMetadata: component.bindings
}
})
return snippet.preamble + snippet.code
case 'script':
return component.code
default:
this.error(`Unknown vue type "${queryString.get('type')}"`)
}
},
async transform (code, id) {
if (!filter(id)) {
return
}
if (id.endsWith('/node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js')) {
return transformOptions(code)
}
if (!id.endsWith('.vue')) {
return
}
const parseResult = parse(code, {
sourceMap: true,
filename: id
})
// Throws an exception without location information, which is handled by
// rollup.
components.set(id, transformScript(parseResult.descriptor, id))
// Each component can have multiple style tags processed with PostCSS.
for (const style of parseResult.descriptor.styles) {
const compiledStyle = await compileStyleAsync({
source: style.content,
filename: id,
id,
inMap: style.map,
isProd: isProduction()
})
styles.push(compiledStyle.rawResult)
}
const source = `import { render } from '${id}?vue&type=template'\n` +
`import ${DEFAULT_VARIABLE} from '${id}?vue&type=script'\n` +
`${DEFAULT_VARIABLE}.render = render\n` +
`${DEFAULT_VARIABLE}.__file = '${id}'\n` +
`export default ${DEFAULT_VARIABLE}\n`
return {
code: source,
map: components.get(id).map
}
},
async generateBundle (options, bundle) {
const root = postcss.document()
for (const compiledStyle of styles) {
const processedStyle = await processor.process(compiledStyle, {
...compiledStyle.opts,
to: path.resolve(`${options.dir}/${cssFileName}`)
})
processedStyle.messages.forEach(message => {
switch (message.type) {
case 'warning':
this.warn({
message: message.text,
loc: { column: message.column, line: message.line },
id: processedStyle.opts.from
})
break
}
})
root.append(processedStyle.root)
}
const mapName = cssFileName + '.map'
const result = root.toResult({
to: cssFileName,
map: {
annotation: mapName
}
})
this.emitFile({
type: 'asset',
id: cssFileName,
fileName: cssFileName,
source: result.css
})
if (options.sourcemap) {
this.emitFile({
type: 'asset',
id: mapName,
fileName: mapName,
source: result.map.toString()
})
}
}
}
}
export default {
input: 'assets/index.js',
output: {
dir: 'dist',
format: 'iife',
sourcemap: true
},
plugins: [
replace({
values: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
},
preventAssignment: true
}),
nodeResolve(),
vuePostcss({
postcssPlugins: [
url({
url: 'copy',
basePath: path.resolve('assets'),
assetsPath: '.',
useHash: false
}),
cssnano()
]
}),
html({
title: 'Hora Bona',
template ({ files, publicPath, title }) {
const contents = fs.readFileSync('assets/index.html')
.toString()
.replace(/<title><\/title>/, `<title>${title}</title>`)
const scripts = files.js.map(function (file) {
return `<script defer src="${publicPath}${file.fileName}"></script>`
})
const styles = files.css.map(function (file) {
return `<link rel="stylesheet" href="${publicPath}${file.fileName}">`
})
const headEnd = contents.search('</head>')
return contents.slice(0, headEnd) +
scripts.join('\n') +
styles.join('\n') +
contents.slice(headEnd)
}
}),
isProduction() && terser() // See: https://github.com/rollup/plugins/issues/1371
]
}