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}`) const scripts = files.js.map(function (file) { return `` }) const styles = files.css.map(function (file) { return `` }) const headEnd = contents.search('') return contents.slice(0, headEnd) + scripts.join('\n') + styles.join('\n') + contents.slice(headEnd) } }), isProduction() && terser() // See: https://github.com/rollup/plugins/issues/1371 ] }