From fc896a399bc5e5c99bd1e1150f907e5c28ba4cd4 Mon Sep 17 00:00:00 2001 From: likui <2218301630@qq.com> Date: Sun, 2 Aug 2020 18:12:31 +0800 Subject: [PATCH] feat(dev): support css sourcemap close #649 --- package.json | 2 ++ src/node/server/serverPluginCss.ts | 23 +++++--------- src/node/server/serverPluginVue.ts | 7 +++-- src/node/utils/cssUtils.ts | 48 ++++++++++++++++++++++++++---- src/node/utils/sourcemap.ts | 38 +++++++++++++++++++++++ yarn.lock | 7 +++++ 6 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 src/node/utils/sourcemap.ts diff --git a/package.json b/package.json index b26aca08e7f43b..705039c6fbb716 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "chalk": "^4.0.0", "chokidar": "^3.3.1", "clean-css": "^4.2.3", + "concat-with-sourcemaps": "^1.1.0", "debug": "^4.1.1", "dotenv": "^8.2.0", "dotenv-expand": "^5.1.0", @@ -96,6 +97,7 @@ "rollup-pluginutils": "^2.8.2", "selfsigned": "^1.10.7", "slash": "^3.0.0", + "source-map": "^0.7.3", "vue": "^3.0.0-rc.5", "ws": "^7.2.3" }, diff --git a/src/node/server/serverPluginCss.ts b/src/node/server/serverPluginCss.ts index 90244b8d6bd098..00f7555af8cbd7 100644 --- a/src/node/server/serverPluginCss.ts +++ b/src/node/server/serverPluginCss.ts @@ -10,13 +10,13 @@ import { getCssImportBoundaries, recordCssImportChain, rewriteCssUrls, - isCSSRequest + isCSSRequest, + ProcessedCSS } from '../utils/cssUtils' import qs from 'querystring' import chalk from 'chalk' import { InternalResolver } from '../resolver' import { clientPublicPath } from './serverPluginClient' - export const debugCSS = require('debug')('vite:css') export const cssPlugin: ServerPlugin = ({ root, app, watcher, resolver }) => { @@ -30,11 +30,12 @@ export const cssPlugin: ServerPlugin = ({ root, app, watcher, resolver }) => { ) { const id = JSON.stringify(hash_sum(ctx.path)) if (isImportRequest(ctx)) { - const { css, modules } = await processCss(root, ctx) + const { css, modules, map } = await processCss(root, ctx) ctx.type = 'js' // we rewrite css with `?import` to a js module that inserts a style // tag linking to the actual raw url ctx.body = codegenCss(id, css, modules) + ctx.map = map } } }) @@ -119,11 +120,6 @@ export const cssPlugin: ServerPlugin = ({ root, app, watcher, resolver }) => { }) } - interface ProcessedCSS { - css: string - modules?: Record - } - // processed CSS is cached in case the user ticks "disable cache" during dev // which can lead to unnecessary processing on page reload const processedCSS = new Map() @@ -139,7 +135,7 @@ export const cssPlugin: ServerPlugin = ({ root, app, watcher, resolver }) => { const filePath = resolver.requestToFile(ctx.path) const preprocessLang = ctx.path.replace(cssPreprocessLangRE, '$2') - const result = await compileCss(root, ctx.path, { + const result = await compileCss(root, ctx.path, ctx.read, { id: '', source: css, filename: filePath, @@ -150,12 +146,6 @@ export const cssPlugin: ServerPlugin = ({ root, app, watcher, resolver }) => { modulesOptions: ctx.config.cssModuleOptions }) - if (typeof result === 'string') { - const res = { css: await rewriteCssUrls(css, ctx.path) } - processedCSS.set(ctx.path, res) - return res - } - recordCssImportChain(result.dependencies, filePath) if (result.errors.length) { @@ -165,7 +155,8 @@ export const cssPlugin: ServerPlugin = ({ root, app, watcher, resolver }) => { const res = { css: await rewriteCssUrls(result.code, ctx.path), - modules: result.modules + modules: result.modules, + map: result.map as any } processedCSS.set(ctx.path, res) return res diff --git a/src/node/server/serverPluginVue.ts b/src/node/server/serverPluginVue.ts index 8832893f647e3a..b791090a2b97e9 100644 --- a/src/node/server/serverPluginVue.ts +++ b/src/node/server/serverPluginVue.ts @@ -157,10 +157,12 @@ export const vuePlugin: ServerPlugin = ({ index, filePath, publicPath, + ctx, config ) ctx.type = 'js' ctx.body = codegenCss(`${id}-${index}`, result.code, result.modules) + ctx.map = result.map as any return etagCacheCheck(ctx) } @@ -589,7 +591,7 @@ function compileSFCTemplate( const result = { code, - map: map as SourceMap + map: map as any } cached = cached || { styles: [], customs: [] } @@ -606,6 +608,7 @@ async function compileSFCStyle( index: number, filePath: string, publicPath: string, + ctx: Context, { cssPreprocessOptions, cssModuleOptions }: ServerPluginContext['config'] ): Promise { let cached = vueCache.get(filePath) @@ -619,7 +622,7 @@ async function compileSFCStyle( const { generateCodeFrame } = resolveCompiler(root) const resource = filePath + `?type=style&index=${index}` - const result = (await compileCss(root, publicPath, { + const result = (await compileCss(root, publicPath, ctx.read, { source: style.content, filename: resource, id: ``, // will be computed in compileCss diff --git a/src/node/utils/cssUtils.ts b/src/node/utils/cssUtils.ts index 63192f74caa58d..5f8be6535037da 100644 --- a/src/node/utils/cssUtils.ts +++ b/src/node/utils/cssUtils.ts @@ -9,6 +9,9 @@ import { SFCAsyncStyleCompileOptions, SFCStyleCompileResults } from '@vue/compiler-sfc' +import { getOriginalSourceMap } from './sourcemap' +import Concat from 'concat-with-sourcemaps' +import { SourceMap } from '../server/serverPluginSourceMap' export const urlRE = /url\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/ export const cssPreprocessLangRE = /(.+)\.(less|sass|scss|styl|stylus|postcss)$/ @@ -51,9 +54,16 @@ export function rewriteCssUrls( }) } +export interface ProcessedCSS { + css: string + modules?: Record + map?: SourceMap +} + export async function compileCss( root: string, publicPath: string, + read: (filePath: string) => Promise, { source, filename, @@ -64,11 +74,16 @@ export async function compileCss( preprocessOptions = {}, modulesOptions = {} }: SFCAsyncStyleCompileOptions, + sourcemap: boolean = true, isBuild: boolean = false -): Promise { +): Promise { const id = hash_sum(publicPath) const postcssConfig = await loadPostcssConfig(root) const { compileStyleAsync } = resolveCompiler(root) + const map = + sourcemap && !filename.includes('.vue') + ? (getOriginalSourceMap(source, filename) as any) + : null if ( publicPath.endsWith('.css') && @@ -78,7 +93,13 @@ export async function compileCss( !source.includes('@import') ) { // no need to invoke compile for plain css if no postcss config is present - return source + return { + code: source, + map, + dependencies: new Set(), + errors: [], + rawResult: undefined + } } const { @@ -106,7 +127,7 @@ export async function compileCss( } } - return await compileStyleAsync({ + const result = await compileStyleAsync({ source, filename, id: `data-v-${id}`, @@ -118,14 +139,31 @@ export async function compileCss( localsConvention: 'camelCase', ...modulesOptions }, - + map, preprocessLang, preprocessCustomRequire: (id: string) => require(resolveFrom(root, id)), preprocessOptions, - postcssOptions, postcssPlugins }) + + if (result.dependencies.size > 0 && sourcemap) { + const concat = new Concat(true, filename, '\n') + if (!filename.includes('.vue')) { + concat.add(filename, source, result.map) + } + for (const dependency of result.dependencies) { + const content = (await read(dependency)).toString() + concat.add( + dependency, + content, + getOriginalSourceMap(content, dependency) as any + ) + } + result.map = JSON.parse(concat.sourceMap!) + } + + return result } // postcss-load-config doesn't expose Result type diff --git a/src/node/utils/sourcemap.ts b/src/node/utils/sourcemap.ts new file mode 100644 index 00000000000000..49dc5f50969b04 --- /dev/null +++ b/src/node/utils/sourcemap.ts @@ -0,0 +1,38 @@ +import { SourceNode, RawSourceMap } from 'source-map' + +const SPLIT_REGEX = /(?!$)[^\n\r;{}]*[\n\r;{}]*/g + +function splitCode(code: string) { + return code.match(SPLIT_REGEX) || [] +} + +export function getOriginalSourceMap( + code: string, + filePath: string +): RawSourceMap { + const lines = code.split('\n') + + const linesSourceNode = lines.map((row, index) => { + let column = 0 + const columnsSourceNode = splitCode( + row + (index !== lines.length - 1 ? '\n' : '') + ).map((item) => { + if (/^\s*$/.test(item)) { + column += item.length + return item + } + const res = new SourceNode(index + 1, column, filePath, item) + column += item.length + return res + }) + return new SourceNode(null, null, null, columnsSourceNode) + }) + + const sourceNode = new SourceNode(null, null, null, linesSourceNode) + + const sourceMapGenerator = sourceNode.toStringWithSourceMap({ + file: filePath + }).map + sourceMapGenerator.setSourceContent(filePath, code) + return sourceMapGenerator.toJSON() +} diff --git a/yarn.lock b/yarn.lock index 0cffc8d797d97e..0fbd0678b98644 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1764,6 +1764,13 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +concat-with-sourcemaps@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" + integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== + dependencies: + source-map "^0.6.1" + consolidate@^0.15.1: version "0.15.1" resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"