From 5725b5ea4c1489fd08227b5bd3b609ee9774c67c Mon Sep 17 00:00:00 2001 From: ygj6 Date: Wed, 2 Mar 2022 16:18:18 +0800 Subject: [PATCH] feat: support modern SASS api --- .../playground/sass-modern/css-dep/index.css | 3 + .../playground/sass-modern/css-dep/index.js | 1 + .../playground/sass-modern/css-dep/index.scss | 3 + .../playground/sass-modern/css-dep/index.styl | 2 + .../sass-modern/css-dep/package.json | 8 + packages/playground/sass-modern/index.html | 8 + packages/playground/sass-modern/main.js | 6 + packages/playground/sass-modern/package.json | 15 ++ packages/playground/sass-modern/sass.scss | 6 + .../playground/sass-modern/vite.config.js | 15 ++ packages/vite/package.json | 2 +- packages/vite/src/node/plugins/css.ts | 150 +++++++++++++----- pnpm-lock.yaml | 40 +++-- 13 files changed, 199 insertions(+), 60 deletions(-) create mode 100644 packages/playground/sass-modern/css-dep/index.css create mode 100644 packages/playground/sass-modern/css-dep/index.js create mode 100644 packages/playground/sass-modern/css-dep/index.scss create mode 100644 packages/playground/sass-modern/css-dep/index.styl create mode 100644 packages/playground/sass-modern/css-dep/package.json create mode 100644 packages/playground/sass-modern/index.html create mode 100644 packages/playground/sass-modern/main.js create mode 100644 packages/playground/sass-modern/package.json create mode 100644 packages/playground/sass-modern/sass.scss create mode 100644 packages/playground/sass-modern/vite.config.js diff --git a/packages/playground/sass-modern/css-dep/index.css b/packages/playground/sass-modern/css-dep/index.css new file mode 100644 index 00000000000000..0fdcd8118ad54c --- /dev/null +++ b/packages/playground/sass-modern/css-dep/index.css @@ -0,0 +1,3 @@ +.css-dep { + color: purple; +} diff --git a/packages/playground/sass-modern/css-dep/index.js b/packages/playground/sass-modern/css-dep/index.js new file mode 100644 index 00000000000000..47b55353d03edb --- /dev/null +++ b/packages/playground/sass-modern/css-dep/index.js @@ -0,0 +1 @@ +throw new Error('should not be imported') diff --git a/packages/playground/sass-modern/css-dep/index.scss b/packages/playground/sass-modern/css-dep/index.scss new file mode 100644 index 00000000000000..97a24aa0551eb9 --- /dev/null +++ b/packages/playground/sass-modern/css-dep/index.scss @@ -0,0 +1,3 @@ +.css-dep-sass { + color: orange; +} diff --git a/packages/playground/sass-modern/css-dep/index.styl b/packages/playground/sass-modern/css-dep/index.styl new file mode 100644 index 00000000000000..68170594a118a9 --- /dev/null +++ b/packages/playground/sass-modern/css-dep/index.styl @@ -0,0 +1,2 @@ +.css-dep-stylus + color red diff --git a/packages/playground/sass-modern/css-dep/package.json b/packages/playground/sass-modern/css-dep/package.json new file mode 100644 index 00000000000000..451510a672485f --- /dev/null +++ b/packages/playground/sass-modern/css-dep/package.json @@ -0,0 +1,8 @@ +{ + "name": "css-dep", + "private": true, + "version": "1.0.0", + "main": "index.js", + "style": "index.css", + "sass": "index.scss" +} diff --git a/packages/playground/sass-modern/index.html b/packages/playground/sass-modern/index.html new file mode 100644 index 00000000000000..578a6c544bfbae --- /dev/null +++ b/packages/playground/sass-modern/index.html @@ -0,0 +1,8 @@ +
+

SASS: This should be orange

+

+ @import dependency w/ sass enrtrypoints: this should be orange +

+
+ + diff --git a/packages/playground/sass-modern/main.js b/packages/playground/sass-modern/main.js new file mode 100644 index 00000000000000..166ecf89244b99 --- /dev/null +++ b/packages/playground/sass-modern/main.js @@ -0,0 +1,6 @@ +import sass from './sass.scss' +text('.imported-sass', sass) + +function text(el, text) { + document.querySelector(el).textContent = text +} diff --git a/packages/playground/sass-modern/package.json b/packages/playground/sass-modern/package.json new file mode 100644 index 00000000000000..ec0d7f27a9af69 --- /dev/null +++ b/packages/playground/sass-modern/package.json @@ -0,0 +1,15 @@ +{ + "name": "test-css", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "debug": "node --inspect-brk ../../vite/bin/vite", + "preview": "vite preview" + }, + "devDependencies": { + "sass": "^1.49.9", + "cross-env": "^7.0.3" + } +} diff --git a/packages/playground/sass-modern/sass.scss b/packages/playground/sass-modern/sass.scss new file mode 100644 index 00000000000000..077f8091c72c54 --- /dev/null +++ b/packages/playground/sass-modern/sass.scss @@ -0,0 +1,6 @@ +//@import 'css-dep'; // package w/ sass entry points + +.sass { + /* injected via vite.config.js */ + color: $injectedColor; +} diff --git a/packages/playground/sass-modern/vite.config.js b/packages/playground/sass-modern/vite.config.js new file mode 100644 index 00000000000000..afe13064352180 --- /dev/null +++ b/packages/playground/sass-modern/vite.config.js @@ -0,0 +1,15 @@ +const path = require('path') + +/** + * @type {import('vite').UserConfig} + */ +module.exports = { + css: { + preprocessorOptions: { + scss: { + api: 'modern', + additionalData: `$injectedColor: orange;` + } + } + } +} diff --git a/packages/vite/package.json b/packages/vite/package.json index e311edaf9527dc..3f0eb8fdf38e8d 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -72,7 +72,6 @@ "@types/mime": "^2.0.3", "@types/node": "^16.11.22", "@types/resolve": "^1.20.1", - "@types/sass": "~1.43.1", "@types/stylus": "^0.48.36", "@types/ws": "^8.2.2", "@vue/compiler-dom": "^3.2.30", @@ -108,6 +107,7 @@ "postcss-modules": "^4.3.0", "resolve.exports": "^1.1.0", "rollup-plugin-license": "^2.6.1", + "sass": "~1.49.9", "sirv": "^2.0.2", "source-map-js": "^1.0.2", "source-map-support": "^0.5.21", diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index cb62b785732e0d..4ba956d8056561 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -988,9 +988,14 @@ type StylePreprocessorOptions = { additionalData?: PreprocessorAdditionalData filename: string alias: Alias[] + api: 'legacy' | 'modern' } -type SassStylePreprocessorOptions = StylePreprocessorOptions & Sass.Options +type SassStylePreprocessorOptions = StylePreprocessorOptions & + Sass.StringOptions<'sync'> + +type SassStylePreprocessorLegacyOptions = StylePreprocessorOptions & + Sass.LegacyOptions<'sync'> type StylePreprocessor = ( source: string, @@ -1002,7 +1007,7 @@ type StylePreprocessor = ( type SassStylePreprocessor = ( source: string, root: string, - options: SassStylePreprocessorOptions, + options: SassStylePreprocessorOptions | SassStylePreprocessorLegacyOptions, resolvers: CSSAtImportResolvers ) => StylePreprocessorResults | Promise @@ -1054,55 +1059,112 @@ const scss: SassStylePreprocessor = async ( options, resolvers ) => { - const render = loadPreprocessor(PreprocessLang.sass, root).render - const internalImporter: Sass.Importer = (url, importer, done) => { - resolvers.sass(url, importer).then((resolved) => { - if (resolved) { - rebaseUrls(resolved, options.filename, options.alias) - .then((data) => done?.(data)) - .catch((data) => done?.(data)) - } else { - done?.(null) + const isModern = options.api === 'modern' + + if (isModern) { + const compileString = loadPreprocessor( + PreprocessLang.sass, + root + ).compileString + + // TODO: we can't execute vite's internal resolver because we don't have prev in new importer API (as we have in old) + const internalImporter: Sass.Importer = { + canonicalize(url, options) { + return null + }, + load(canonicalUrl) { + return null } - }) - } - const importer = [internalImporter] - if (options.importer) { - Array.isArray(options.importer) - ? importer.push(...options.importer) - : importer.push(options.importer) - } + } + const importers = [internalImporter] + if (options.importers && Array.isArray(options.importers)) { + importers.push(...options.importers) + } - const finalOptions: Sass.Options = { - ...options, - data: await getSource(source, options.filename, options.additionalData), - file: options.filename, - outFile: options.filename, - importer - } + if (typeof options.syntax === 'undefined') { + const ext = path.extname(options.filename) - try { - const result = await new Promise((resolve, reject) => { - render(finalOptions, (err, res) => { - if (err) { - reject(err) + if (ext && ext.toLowerCase() === '.scss') { + options.syntax = 'scss' + } else if (ext && ext.toLowerCase() === '.sass') { + options.syntax = 'indented' + } else if (ext && ext.toLowerCase() === '.css') { + options.syntax = 'css' + } + } + + const finalOptions: Sass.StringOptions<'sync'> = { + ...(options as Sass.StringOptions<'sync'>), + url: new URL(options.filename), + importers + } + + try { + const result = compileString( + await getSource(source, options.filename, options.additionalData), + finalOptions + ) + const deps = result.loadedUrls.map((url) => url.pathname) + + return { + code: result.css, + errors: [], + deps + } + } catch (e) { + return { code: '', errors: [e], deps: [] } + } + } else { + const render = loadPreprocessor(PreprocessLang.sass, root).render + const internalImporter: Sass.LegacyImporter = (url, importer, done) => { + resolvers.sass(url, importer).then((resolved) => { + if (resolved) { + rebaseUrls(resolved, options.filename, options.alias) + .then((data) => done?.(data)) + .catch((data) => done?.(data)) } else { - resolve(res) + done?.(null) } }) - }) - const deps = result.stats.includedFiles + } + const importer = [internalImporter] + if (options.importer) { + Array.isArray(options.importer) + ? importer.push(...options.importer) + : importer.push(options.importer) + } - return { - code: result.css.toString(), - errors: [], - deps + const finalOptions: Sass.LegacyOptions<'async'> = { + ...(options as Sass.LegacyOptions<'sync'>), + data: await getSource(source, options.filename, options.additionalData), + file: options.filename, + outFile: options.filename, + importer + } + + try { + const result = await new Promise((resolve, reject) => { + render(finalOptions, (err, res) => { + if (err) { + reject(err) + } else { + resolve(res!) + } + }) + }) + const deps = result.stats.includedFiles + + return { + code: result.css.toString(), + errors: [], + deps + } + } catch (e) { + // normalize SASS error + e.id = e.file + e.frame = e.formatted + return { code: '', errors: [e], deps: [] } } - } catch (e) { - // normalize SASS error - e.id = e.file - e.frame = e.formatted - return { code: '', errors: [e], deps: [] } } } @@ -1125,7 +1187,7 @@ async function rebaseUrls( file: string, rootFile: string, alias: Alias[] -): Promise { +): Promise { file = path.resolve(file) // ensure os-specific flashes // in the same dir, no need to rebase const fileDir = path.dirname(file) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab2b4a7833c686..c8adf9cf8d53e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -453,6 +453,17 @@ importers: packages/playground/resolve/inline-package: specifiers: {} + packages/playground/sass-modern: + specifiers: + cross-env: ^7.0.3 + sass: ^1.49.9 + devDependencies: + cross-env: 7.0.3 + sass: 1.49.9 + + packages/playground/sass-modern/css-dep: + specifiers: {} + packages/playground/ssr-deps: specifiers: bcrypt: ^5.0.1 @@ -757,7 +768,6 @@ importers: '@types/mime': ^2.0.3 '@types/node': ^16.11.22 '@types/resolve': ^1.20.1 - '@types/sass': ~1.43.1 '@types/stylus': ^0.48.36 '@types/ws': ^8.2.2 '@vue/compiler-dom': ^3.2.30 @@ -798,6 +808,7 @@ importers: resolve.exports: ^1.1.0 rollup: ^2.59.0 rollup-plugin-license: ^2.6.1 + sass: ~1.49.9 sirv: ^2.0.2 source-map-js: ^1.0.2 source-map-support: ^0.5.21 @@ -835,7 +846,6 @@ importers: '@types/mime': 2.0.3 '@types/node': 16.11.22 '@types/resolve': 1.20.1 - '@types/sass': 1.43.1 '@types/stylus': 0.48.36 '@types/ws': 8.2.2 '@vue/compiler-dom': 3.2.30 @@ -871,6 +881,7 @@ importers: postcss-modules: 4.3.0_postcss@8.4.6 resolve.exports: 1.1.0 rollup-plugin-license: 2.6.1_rollup@2.62.0 + sass: 1.49.9 sirv: 2.0.2 source-map-js: 1.0.2 source-map-support: 0.5.21 @@ -2439,12 +2450,6 @@ packages: resolution: {integrity: sha512-Ku5+GPFa12S3W26Uwtw+xyrtIpaZsGYHH6zxNbZlstmlvMYSZRzOwzwsXbxlVUbHyUucctSyuFtu6bNxwYomIw==} dev: true - /@types/sass/1.43.1: - resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==} - dependencies: - '@types/node': 16.11.22 - dev: true - /@types/semver/7.3.9: resolution: {integrity: sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==} dev: true @@ -8078,9 +8083,19 @@ packages: engines: {node: '>=8.9.0'} hasBin: true dependencies: - chokidar: 3.5.2 + chokidar: 3.5.3 immutable: 4.0.0 - source-map-js: 1.0.1 + source-map-js: 1.0.2 + dev: true + + /sass/1.49.9: + resolution: {integrity: sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==} + engines: {node: '>=12.0.0'} + hasBin: true + dependencies: + chokidar: 3.5.3 + immutable: 4.0.0 + source-map-js: 1.0.2 dev: true /sax/1.2.4: @@ -8292,11 +8307,6 @@ packages: smart-buffer: 4.2.0 dev: true - /source-map-js/1.0.1: - resolution: {integrity: sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==} - engines: {node: '>=0.10.0'} - dev: true - /source-map-js/1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'}