diff --git a/.commitlintrc.json b/.commitlintrc.json new file mode 100644 index 0000000..b4eb7e9 --- /dev/null +++ b/.commitlintrc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json.schemastore.org/commitlintrc.json", + "extends": ["@commitlint/config-conventional"] +} diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/CI.yml similarity index 59% rename from .github/workflows/build-and-test.yml rename to .github/workflows/CI.yml index c56c778..41140d3 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/CI.yml @@ -1,17 +1,16 @@ # This workflow performs a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions -name: Build and Test +name: CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main, dev ] + branches: [main, dev] jobs: build-and-test: - runs-on: ubuntu-latest strategy: @@ -20,19 +19,41 @@ jobs: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' - run: npm ci + - run: npm run output-coverage-lcov - name: Coveralls uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} - file: "coverage/tests.lcov" + file: 'coverage/tests.lcov' + + code-check: + runs-on: ubuntu-latest + + strategy: + matrix: + script: ['format'] + + name: Code check (${{ matrix.script }}) + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - run: npm ci --ignore-scripts + + - run: npm run ${{matrix.script}} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3fdc069..a03141a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,4 +21,3 @@ jobs: - run: npm publish env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..b2d1ed4 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +CHANGELOG.md +test/assets/** +test/fixtures/** +test/snapshots/** diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..84cc551 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "singleQuote": true +} diff --git a/README.md b/README.md index af25040..ec8d7d1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -rollup-plugin-sass [![Build and Test](https://github.com/elycruz/rollup-plugin-sass/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/elycruz/rollup-plugin-sass/actions/workflows/build-and-test.yml) [![issues](https://img.shields.io/github/issues/elycruz/rollup-plugin-sass.svg?style=flat-square)](https://www.npmjs.com/package/rollup-plugin-sass) [![npm](https://img.shields.io/npm/v/rollup-plugin-sass.svg?style=flat-square)](https://www.npmjs.com/package/rollup-plugin-sass) [![mit](https://img.shields.io/npm/l/rollup-plugin-sass.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![Coverage Status](https://coveralls.io/repos/github/elycruz/rollup-plugin-sass/badge.svg?branch=main)](https://coveralls.io/github/elycruz/rollup-plugin-sass?branch=main) -===== +# rollup-plugin-sass [![CI](https://github.com/elycruz/rollup-plugin-sass/actions/workflows/CI.yml/badge.svg)](https://github.com/elycruz/rollup-plugin-sass/actions/workflows/CI.yml) [![issues](https://img.shields.io/github/issues/elycruz/rollup-plugin-sass.svg?style=flat-square)](https://www.npmjs.com/package/rollup-plugin-sass) [![npm](https://img.shields.io/npm/v/rollup-plugin-sass.svg?style=flat-square)](https://www.npmjs.com/package/rollup-plugin-sass) [![mit](https://img.shields.io/npm/l/rollup-plugin-sass.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![Coverage Status](https://coveralls.io/repos/github/elycruz/rollup-plugin-sass/badge.svg?branch=main)](https://coveralls.io/github/elycruz/rollup-plugin-sass?branch=main) ## Installation @@ -19,10 +18,8 @@ export default { file: 'bundle.js', format: 'cjs', }, - plugins: [ - sass() - ] -} + plugins: [sass()], +}; ``` #### rollup.config.ts @@ -54,7 +51,7 @@ Profit. ### `output` -+ Type: `Boolean|String|Function` _(default: false)_ +- Type: `Boolean|String|Function` _(default: false)_ ```js sass({ @@ -76,21 +73,21 @@ sass({ // ] output(styles, styleNodes) { writeFileSync('bundle.css', styles); - } -}) + }, +}); ``` ### `insert` -+ Type: `Boolean` _(default: false)_ +- Type: `Boolean` _(default: false)_ If you specify `true`, the plugin will insert compiled CSS into `` tag, via utility function that it will output in your build bundle. ```js sass({ - insert: true -}) + insert: true, +}); ``` **Usage caveat:** @@ -98,15 +95,15 @@ sass({ There is a utility function that handles injecting individual style payloads into the page's head, which is output as `___$insertStyle` by the rollup-plugin-sass plugin. This function is output to `./dist/node_modules/...`, in user-land builds, so you have to make sure that it isn't -ignored by your build tool(s) (E.g., rollup, webpack etc.); As a solution, you'll just have to make sure that the -directory is "included"/not-"excluded" via your build tools facilities/added-plugins/etc. +ignored by your build tool(s) (E.g., rollup, webpack etc.); As a solution, you'll just have to make sure that the +directory is "included"/not-"excluded" via your build tools facilities/added-plugins/etc. -Additionally, if you're publishing an app to an internal registry, or similar, you'll have to +Additionally, if you're publishing an app to an internal registry, or similar, you'll have to make sure 'dist/node_modules' isn't ignored in this scenario as well. ### `processor` -+ Type: `Function` +- Type: `Function` If you specify a function as processor which will be called with compiled css before generate phase. @@ -118,10 +115,11 @@ sass({ // Processor will be called with two arguments: // - style: the compiled css // - id: import id - processor: css => postcss([autoprefixer]) - .process(css) - .then(result => result.css) -}) + processor: (css) => + postcss([autoprefixer]) + .process(css) + .then((result) => result.css), +}); ``` The `processor` also support object result. Reverse `css` filLed for stylesheet, the rest of the properties can be customized. @@ -130,12 +128,12 @@ The `processor` also support object result. Reverse `css` filLed for stylesheet, sass({ processor(code) { return { - css: '.body {}', - foo: 'foo', - bar: 'bar', + css: '.body {}', + foo: 'foo', + bar: 'bar', }; }, -}) +}); ``` Otherwise, you could do: @@ -144,31 +142,32 @@ Otherwise, you could do: import style, { foo, bar } from 'stylesheet'; ``` -#### Exporting sass variable to *.js +#### Exporting sass variable to \*.js Example showing how to use [icss-utils](https://www.npmjs.com/package/icss-utils) to extract resulting sass vars -to your *.js bundle: +to your \*.js bundle: ```js const config = { input: 'test/fixtures/processor-promise/with-icss-exports.js', plugins: [ sass({ - processor: (css) => new Promise((resolve, reject) => { - const pcssRootNodeRslt = postcss.parse(css), - extractedIcss = extractICSS(pcssRootNodeRslt, true), - cleanedCss = pcssRootNodeRslt.toString(), - out = Object.assign({}, extractedIcss.icssExports, { - css: cleanedCss, - }); - // console.table(extractedIcss); - // console.log(out); - resolve(out); - }), + processor: (css) => + new Promise((resolve, reject) => { + const pcssRootNodeRslt = postcss.parse(css), + extractedIcss = extractICSS(pcssRootNodeRslt, true), + cleanedCss = pcssRootNodeRslt.toString(), + out = Object.assign({}, extractedIcss.icssExports, { + css: cleanedCss, + }); + // console.table(extractedIcss); + // console.log(out); + resolve(out); + }), options: sassOptions, }), ], -} +}; ``` See the [Input file](test/fixtures/processor-promise/with-icss-exports.js) for example on how to access @@ -176,13 +175,13 @@ the exported vars. ### `runtime` -+ Type: `Object` _(default: sass)_ +- Type: `Object` _(default: sass)_ If you specify an object, it will be used instead of [sass](https://github.com/sass/dart-sass). You can use this to pass a different sass compiler (for example the `node-sass` npm package). ### `options` -+ Type: `Object` +- Type: `Object` Options for [sass](https://github.com/sass/dart-sass) or your own runtime sass compiler. @@ -192,38 +191,38 @@ Since you can inject variables during sass compilation with node. ```js sass({ options: { - data: '$color: #000;' - } -}) + data: '$color: #000;', + }, +}); ``` ### `include` -+ Type: `string | string[]` -+ Default: `['**/*.sass', '**/*.scss']` +- Type: `string | string[]` +- Default: `['**/*.sass', '**/*.scss']` Glob of sass/css files to be targeted. ```ts sass({ - include: ['**/*.css', '**/*.sass', '**/*.scss'] -}) + include: ['**/*.css', '**/*.sass', '**/*.scss'], +}); ``` ### `exclude` -+ Type: `string | string[]`; -+ Default: `'node_modules/**'` +- Type: `string | string[]`; +- Default: `'node_modules/**'` Globs to exclude from processing. ```ts sass({ - exclude: 'node_modules/**' -}) + exclude: 'node_modules/**', +}); ``` ## License -[MIT](./LICENSE) [elycruz](https://github.com/elycruz), +[MIT](./LICENSE) [elycruz](https://github.com/elycruz), [BinRui.Guan](mailto:differui@gmail.com) diff --git a/commitlint.config.mjs b/commitlint.config.mjs deleted file mode 100644 index 3f5e287..0000000 --- a/commitlint.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export default { extends: ['@commitlint/config-conventional'] }; diff --git a/package-lock.json b/package-lock.json index e25ce5e..7812acb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "icss-utils": "^5.1.0", "nyc": "^17.0.0", "postcss": "^8.4.16", + "prettier": "^3.3.3", "rollup": "^1 || ^2 || ^3", "sinon": "^18.0.0", "ts-node": "^10.9.1", @@ -4789,6 +4790,22 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-ms": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.0.0.tgz", diff --git a/package.json b/package.json index 06664a0..46be07b 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,9 @@ "coverage": "nyc report --reporter=text-lcov | coveralls", "output-coverage-lcov": "nyc report --reporter=text-lcov > coverage/tests.lcov", "test:rollup.config.spec.ts": "tsc --project tsconfig.spec.json --noEmit", - "commitlint": "commitlint --edit" + "commitlint": "commitlint --edit", + "format": "prettier . --check", + "format:fix": "prettier . --write" }, "standard": { "ignore": [ @@ -51,7 +53,8 @@ ], "ava": { "files": [ - "./test/*.test.ts" + "./test/*.test.ts", + "!./test/rollup.config.test.ts" ], "extensions": [ "ts" @@ -82,6 +85,7 @@ "icss-utils": "^5.1.0", "nyc": "^17.0.0", "postcss": "^8.4.16", + "prettier": "^3.3.3", "rollup": "^1 || ^2 || ^3", "sinon": "^18.0.0", "ts-node": "^10.9.1", diff --git a/rollup.config.spec.ts b/rollup.config.spec.ts deleted file mode 100644 index e50e1b7..0000000 --- a/rollup.config.spec.ts +++ /dev/null @@ -1,6 +0,0 @@ -import sass from './src/index'; - -const instance = sass(); - -// Should pass `tsc` dry-run (`tsc ... --noEmit`) with no errors. - diff --git a/scripts/clean-and-run-downlevel-dts.js b/scripts/clean-and-run-downlevel-dts.js index 3b92397..e69810a 100644 --- a/scripts/clean-and-run-downlevel-dts.js +++ b/scripts/clean-and-run-downlevel-dts.js @@ -3,53 +3,58 @@ * @description Removes '{repo-root}/ts3.5' directory, re-creates it, runs * `npm run downlevel-dts` from repo-root, and copies the root tsconfig.json file into new directory. */ -const fs = require('fs').promises, - path = require('path'), - {spawn} = require('child_process'), +const fs = require('fs').promises; +const path = require('path'); +const { spawn } = require('child_process'); - {log, warn, error} = console, - - rootDir = path.join(__dirname, '..'), - outputDir = path.resolve(path.join(__dirname, '../ts3.5')), - - tsConfigFilePath = path.join(rootDir, 'tsconfig.json'), - tsConfigOutFilePath = path.join(outputDir, 'tsconfig.json'); +const { log, warn, error } = console; +const rootDir = path.join(__dirname, '..'); +const outputDir = path.resolve(path.join(__dirname, '../ts3.5')); +const tsConfigFilePath = path.join(rootDir, 'tsconfig.json'); +const tsConfigOutFilePath = path.join(outputDir, 'tsconfig.json'); // Run 'clean-and-run' process (async () => - // @note Conditional check here since we have a relaxed 'package.json.engines.node' value, // and `fs.rmdir` is being deprecated in later versions of node (node v18+). // ---- - (fs.rm ? fs.rm : fs.rmdir)(outputDir, {recursive: true}) - + (fs.rm ? fs.rm : fs.rmdir)(outputDir, { recursive: true }) // If error is any other than "file doesn't exist"/"ENOENT" ensure error is thrown. - .catch(err => err.code !== 'ENOENT' ? error(err) : null) + .catch((err) => (err.code !== 'ENOENT' ? error(err) : null)) .then(() => fs.mkdir(outputDir)) .then(() => fs.copyFile(tsConfigFilePath, tsConfigOutFilePath)) // Run downlevel-dts - .then(() => new Promise((resolve, reject) => { - - // Start downlevel-dts package script - const subProcess = spawn('npm', ['run', 'downlevel-dts'], {cwd: rootDir}); - - // Log child process buffer data as `string` - subProcess.stdout.on('data', data => log(data.toString().trim() + '\n')); - - // Log stderr, child process, buffer data as a `string` - subProcess.stderr.on('data', data => warn(data.toString().trim() + '\n')); - - // Handle process end - subProcess.on('close', (code) => code !== 0 ? - reject(`Child process existed with code ${code}.\n`) : - resolve('"clean-and-run-downlevel-dts" completed successfully.\n') - ); - - // Catch process start errors - subProcess.on('error', reject); - })) - -)().then(log, error); + .then( + () => + new Promise((resolve, reject) => { + // Start downlevel-dts package script + const subProcess = spawn('npm', ['run', 'downlevel-dts'], { + cwd: rootDir, + }); + + // Log child process buffer data as `string` + subProcess.stdout.on('data', (data) => + log(data.toString().trim() + '\n'), + ); + + // Log stderr, child process, buffer data as a `string` + subProcess.stderr.on('data', (data) => + warn(data.toString().trim() + '\n'), + ); + + // Handle process end + subProcess.on('close', (code) => + code !== 0 + ? reject(`Child process existed with code ${code}.\n`) + : resolve( + '"clean-and-run-downlevel-dts" completed successfully.\n', + ), + ); + + // Catch process start errors + subProcess.on('error', reject); + }), + ))().then(log, error); diff --git a/src/index.ts b/src/index.ts index 1552467..5cc4989 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,9 @@ -import {promisify} from 'util'; +import { promisify } from 'util'; import resolve from 'resolve'; import * as sass from 'sass'; -import {dirname} from 'path'; +import { dirname } from 'path'; import * as fs from 'fs'; -import {createFilter} from '@rollup/pluginutils'; +import { createFilter } from '@rollup/pluginutils'; import type { SassImporterResult, RollupAssetInfo, @@ -12,108 +12,130 @@ import type { RollupPluginSassOutputFn, SassOptions, RollupPluginSassProcessorFnOutput, - SassRenderResult -} from "./types"; -import {isFunction, isObject, isString, warn} from "./utils"; + SassRenderResult, +} from './types'; +import { isFunction, isObject, isString, warn } from './utils'; // @note Rollup is added as a "devDependency" so no actual symbols should be imported. // Interfaces and non-concrete types are ok. -import {Plugin as RollupPlugin} from 'rollup'; +import { Plugin as RollupPlugin } from 'rollup'; type PluginState = { // Stores interim bundle objects - styles: { id?: string, content?: string }[], + styles: { id?: string; content?: string }[]; // ""; Used, currently to ensure that we're not pushing style objects representing // the same file-path into `pluginState.styles` more than once. - styleMaps:{ [index: string]: { id?: string, content?: string } } + styleMaps: { [index: string]: { id?: string; content?: string } }; }; -const MATCH_SASS_FILENAME_RE = /\.sass$/, +const MATCH_SASS_FILENAME_RE = /\.sass$/; +const MATCH_NODE_MODULE_RE = /^~([a-z0-9]|@).+/i; - MATCH_NODE_MODULE_RE = /^~([a-z0-9]|@).+/i, +const insertFnName = '___$insertStyle'; - insertFnName = '___$insertStyle', +/** + * Returns a sass `importer` list: + * @see https://sass-lang.com/documentation/js-api#importer + */ +const getImporterList = (sassOptions: SassOptions) => { + // `Promise` to chain all `importer1` calls to; E.g., subsequent `importer1` calls won't call `done` until previous `importer1` calls have called `done` (import order enforcement) - Required since importer below is actually 'async'. + let lastResult = Promise.resolve(); /** - * Returns a sass `importer` list: - * @see https://sass-lang.com/documentation/js-api#importer + * Legacy Sass (*.scss/*.sass) file importer (works in new (< v2.0), and older, versions of `sass` (dart-sass) module). + * + * @see https://sass-lang.com/documentation/js-api/modules#LegacyAsyncImporter + * + * @param {string} url - Url found in `@import`/`@use`, found in parent sass file; E.g., exactly as it appears in sass file. + * @param {string} prevUrl - Url of file that contains '@import' rule for incoming file (`url`). + * @param {(result: LegacyImporterResult | SassImporterResult) => void} done - Signals import completion. Note: `LegacyImporterResult`, and `SassImporterResult`, are the same here - We've defined the type for our plugin, since older versions of sass don't have this type defined. + * @note This importer may not work in dart-sass v2.0+ (which may be far off in the future, but is important to note: https://sass-lang.com/documentation/js-api/#legacy-api). + * @returns {void} */ - getImporterList = (sassOptions: SassOptions) => { - // `Promise` to chain all `importer1` calls to; E.g., subsequent `importer1` calls won't call `done` until previous `importer1` calls have called `done` (import order enforcement) - Required since importer below is actually 'async'. - let lastResult = Promise.resolve(); - - /** - * Legacy Sass (*.scss/*.sass) file importer (works in new (< v2.0), and older, versions of `sass` (dart-sass) module). - * - * @see https://sass-lang.com/documentation/js-api/modules#LegacyAsyncImporter - * - * @param {string} url - Url found in `@import`/`@use`, found in parent sass file; E.g., exactly as it appears in sass file. - * @param {string} prevUrl - Url of file that contains '@import' rule for incoming file (`url`). - * @param {(result: LegacyImporterResult | SassImporterResult) => void} done - Signals import completion. Note: `LegacyImporterResult`, and `SassImporterResult`, are the same here - We've defined the type for our plugin, since older versions of sass don't have this type defined. - * @note This importer may not work in dart-sass v2.0+ (which may be far off in the future, but is important to note: https://sass-lang.com/documentation/js-api/#legacy-api). - * @returns {void} - */ - const importer1 = (url: string, prevUrl: string, done: (rslt: SassImporterResult) => void): void | null => { - if (!MATCH_NODE_MODULE_RE.test(url)) { - return null; - } - - const moduleUrl = url.slice(1); - const resolveOptions = { - basedir: dirname(prevUrl), - extensions: ['.scss', '.sass'], - }; - - // @todo This block should run as a promise instead, will help ensure we're not blocking the thread it is - // running on, even though `sass` is probably already running the importer in one. - try { - const file = resolve.sync(moduleUrl, resolveOptions); - lastResult = lastResult.then(() => done({file})); - } catch (err) { - warn('[rollup-plugin-sass]: Recovered from error: ', err); - // If importer has sibling importers then exit and allow one of the other - // importers to attempt file path resolution. - if (sassOptions.importer && sassOptions.importer.length > 1) { - lastResult = lastResult.then(() => done(null)); - return; - } - lastResult = lastResult.then(() => done({ - file: url, - })); - } + const importer1 = ( + url: string, + prevUrl: string, + done: (rslt: SassImporterResult) => void, + ): void | null => { + if (!MATCH_NODE_MODULE_RE.test(url)) { + return null; + } + + const moduleUrl = url.slice(1); + const resolveOptions = { + basedir: dirname(prevUrl), + extensions: ['.scss', '.sass'], }; - return [importer1].concat(sassOptions.importer as Extract || []); - }, - - processRenderResponse = (rollupOptions: Pick, file: string, state: PluginState, inCss: string) => { - if (!inCss) return Promise.resolve(); - - const {processor} = rollupOptions; - - return Promise.resolve() - .then(() => !isFunction(processor) ? inCss + '' : processor(inCss, file)) + // @todo This block should run as a promise instead, will help ensure we're not blocking the thread it is + // running on, even though `sass` is probably already running the importer in one. + try { + const file = resolve.sync(moduleUrl, resolveOptions); + lastResult = lastResult.then(() => done({ file })); + } catch (err) { + warn('[rollup-plugin-sass]: Recovered from error: ', err); + // If importer has sibling importers then exit and allow one of the other + // importers to attempt file path resolution. + if (sassOptions.importer && sassOptions.importer.length > 1) { + lastResult = lastResult.then(() => done(null)); + return; + } + lastResult = lastResult.then(() => + done({ + file: url, + }), + ); + } + }; + + return [importer1].concat( + (sassOptions.importer as Extract) || [], + ); +}; +const processRenderResponse = ( + rollupOptions: Pick< + RollupPluginSassOptions, + 'insert' | 'processor' | 'output' + >, + file: string, + state: PluginState, + inCss: string, +) => { + if (!inCss) return Promise.resolve(); + + const { processor } = rollupOptions; + + return ( + Promise.resolve() + .then(() => + !isFunction(processor) ? inCss + '' : processor(inCss, file), + ) // Gather output requirements .then((result: Partial) => { if (!isObject(result)) { return [result, '']; } if (!isString(result.css)) { - throw new Error('You need to return the styles using the `css` property. ' + - 'See https://github.com/differui/rollup-plugin-sass#processor'); + throw new Error( + 'You need to return the styles using the `css` property. ' + + 'See https://github.com/differui/rollup-plugin-sass#processor', + ); } const outCss = result.css; delete result.css; - const restExports = Object.keys(result).reduce((agg, name) => - agg + `export const ${name} = ${JSON.stringify(result[name])};\n`, ''); + const restExports = Object.keys(result).reduce( + (agg, name) => + agg + `export const ${name} = ${JSON.stringify(result[name])};\n`, + '', + ); return [outCss, restExports]; }) // Compose output .then(([resolvedCss, restExports]) => { - const {styleMaps} = state; + const { styleMaps } = state; // Update bundle tracking entry with resolved content styleMaps[file].content = resolvedCss; @@ -136,35 +158,37 @@ const MATCH_SASS_FILENAME_RE = /\.sass$/, } return `${imports}export default ${defaultExport};\n${restExports}`; - }); // @note do not `catch` here - let error propagate to rollup level - }, - - defaultIncludes = ['**/*.sass', '**/*.scss'], + }) + ); // @note do not `catch` here - let error propagate to rollup level +}; - defaultExcludes = 'node_modules/**'; +const defaultIncludes = ['**/*.sass', '**/*.scss']; +const defaultExcludes = 'node_modules/**'; // Typescript syntax for CommonJs "default exports" compatible export // @reference https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require -export = function plugin(options = {} as RollupPluginSassOptions): RollupPlugin { - const pluginOptions = Object.assign({ +export = function plugin( + options = {} as RollupPluginSassOptions, +): RollupPlugin { + const pluginOptions = Object.assign( + { runtime: sass, output: false, - insert: false - }, options), - - { - include = defaultIncludes, - exclude = defaultExcludes, - runtime: sassRuntime, - options: incomingSassOptions = {} as SassOptions - } = pluginOptions, - - filter = createFilter(include || '', exclude || ''), - - pluginState: PluginState = { - styles: [], - styleMaps: {} - }; + insert: false, + }, + options, + ); + const { + include = defaultIncludes, + exclude = defaultExcludes, + runtime: sassRuntime, + options: incomingSassOptions = {} as SassOptions, + } = pluginOptions; + const filter = createFilter(include || '', exclude || ''); + const pluginState: PluginState = { + styles: [], + styleMaps: {}, + }; return { name: 'rollup-plugin-sass', @@ -173,17 +197,15 @@ export = function plugin(options = {} as RollupPluginSassOptions): RollupPlugin if (!filter(filePath)) { return Promise.resolve(); } - const paths = [dirname(filePath), process.cwd()], - - {styleMaps, styles} = pluginState, - - resolvedOptions = Object.assign({}, incomingSassOptions, { - file: filePath, - data: incomingSassOptions.data && `${incomingSassOptions.data}${code}`, - indentedSyntax: MATCH_SASS_FILENAME_RE.test(filePath), - includePaths: (incomingSassOptions.includePaths || []).concat(paths), - importer: getImporterList(incomingSassOptions), - }); + const paths = [dirname(filePath), process.cwd()]; + const { styleMaps, styles } = pluginState; + const resolvedOptions = Object.assign({}, incomingSassOptions, { + file: filePath, + data: incomingSassOptions.data && `${incomingSassOptions.data}${code}`, + indentedSyntax: MATCH_SASS_FILENAME_RE.test(filePath), + includePaths: (incomingSassOptions.includePaths || []).concat(paths), + importer: getImporterList(incomingSassOptions), + }); // Setup resolved css output bundle tracking, for use later in `generateBundle` method. // ---- @@ -197,39 +219,57 @@ export = function plugin(options = {} as RollupPluginSassOptions): RollupPlugin } return promisify(sassRuntime.render.bind(sassRuntime))(resolvedOptions) - .then((res: SassRenderResult) => processRenderResponse(pluginOptions, filePath, pluginState, res.css.toString().trim()) - .then(result => [res, result]) + .then((res: SassRenderResult) => + processRenderResponse( + pluginOptions, + filePath, + pluginState, + res.css.toString().trim(), + ).then((result) => [res, result]), ) - .then(([res, codeResult]: [SassRenderResult, RollupPluginSassProcessorFnOutput]) => { + .then( + ([res, codeResult]: [ + SassRenderResult, + RollupPluginSassProcessorFnOutput, + ]) => { // @todo Do we need to filter this call so it only occurs when rollup is in 'watch' mode? res.stats.includedFiles.forEach((filePath: string) => { this.addWatchFile(filePath); }); return { - code: codeResult || '', - map: {mappings: res.map ? res.map.toString() : ''} + code: codeResult || '', + map: { mappings: res.map ? res.map.toString() : '' }, }; - }); // @note do not `catch` here - let error propagate to rollup level. + }, + ); // @note do not `catch` here - let error propagate to rollup level. }, - generateBundle(generateOptions: { file?: string }, - bundle: { [fileName: string]: RollupAssetInfo | RollupChunkInfo }, - isWrite: boolean + generateBundle( + generateOptions: { file?: string }, + bundle: { [fileName: string]: RollupAssetInfo | RollupChunkInfo }, + isWrite: boolean, ): Promise { - if (!isWrite || (!pluginOptions.insert && (!pluginState.styles.length || pluginOptions.output === false))) { + if ( + !isWrite || + (!pluginOptions.insert && + (!pluginState.styles.length || pluginOptions.output === false)) + ) { return Promise.resolve(); } - const {styles} = pluginState, - css = styles.map(style => style.content).join(''), - {output, insert} = pluginOptions; + const { styles } = pluginState; + const css = styles.map((style) => style.content).join(''); + const { output, insert } = pluginOptions; if (typeof output === 'string') { - return fs.promises.mkdir(dirname(output as string), {recursive: true}) + return fs.promises + .mkdir(dirname(output as string), { recursive: true }) .then(() => fs.promises.writeFile(output as string, css)); } else if (typeof output === 'function') { - return Promise.resolve((output as RollupPluginSassOutputFn)(css, styles)); + return Promise.resolve( + (output as RollupPluginSassOutputFn)(css, styles), + ); } else if (!insert && generateOptions.file && output === true) { let dest = generateOptions.file; @@ -237,11 +277,12 @@ export = function plugin(options = {} as RollupPluginSassOptions): RollupPlugin dest = dest.slice(0, -3); } dest = `${dest}.css`; - return fs.promises.mkdir(dirname(dest), {recursive: true}) + return fs.promises + .mkdir(dirname(dest), { recursive: true }) .then(() => fs.promises.writeFile(dest, css)); } return Promise.resolve(css); }, } as RollupPlugin; -} +}; diff --git a/src/insertStyle.ts b/src/insertStyle.ts index 5165781..1ac581d 100644 --- a/src/insertStyle.ts +++ b/src/insertStyle.ts @@ -8,7 +8,9 @@ * * @return css style */ -export default function insertStyle(css: string | undefined): string | undefined { +export default function insertStyle( + css: string | undefined, +): string | undefined { if (!css || typeof window === 'undefined') { return; } diff --git a/src/types.ts b/src/types.ts index c4663cf..712cd7e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,43 +1,47 @@ -import type {LegacyOptions, LegacyResult, types} from 'sass'; +import type { LegacyOptions, LegacyResult, types } from 'sass'; export interface IdAndContentObject { - id?: string, - content?: string + id?: string; + content?: string; } -export type RollupPluginSassOutputFn = (styles: string, styleNodes: IdAndContentObject[]) => any; +export type RollupPluginSassOutputFn = ( + styles: string, + styleNodes: IdAndContentObject[], +) => any; export type RollupPluginSassProcessorFnOutput = - string | - { - css: string, - // User processor might add additional exports - [key: string]: unknown - }; -export type RollupPluginSassProcessorFn = (styles: string, id: string) => Promise | T; + | string + | { + css: string; + // User processor might add additional exports + [key: string]: unknown; + }; +export type RollupPluginSassProcessorFn = + (styles: string, id: string) => Promise | T; export interface RollupPluginSassOptions { /** * File globs to "exclude" from processing. Default 'node_modules/**'. */ - exclude?: string | string[], + exclude?: string | string[]; /** * File globs to include in processing. Default `['**\/*.sass', '**\/*.scss']`, */ - include?: string | string[], + include?: string | string[]; /** * Controls whether to insert generated styles into a style tag on (html) page's `head` or not. */ - insert?: boolean, + insert?: boolean; /** * Options to pass to resolved sass runtime instance (node-sass/sass etc.). */ - options?: SassOptions, + options?: SassOptions; - processor?: RollupPluginSassProcessorFn, + processor?: RollupPluginSassProcessorFn; /** * Controls where sass output is generated to. If `false`, the default, output is generated at the resolved location @@ -52,30 +56,44 @@ export interface RollupPluginSassOptions { * } * ``` */ - output?: boolean | string | RollupPluginSassOutputFn, + output?: boolean | string | RollupPluginSassOutputFn; /** * Sass runtime instance - sass, node-sass or other etc.. */ - runtime?: any, + runtime?: any; } -export type SassImporterResult = { file: string } | { contents: string } | Error | null; +export type SassImporterResult = + | { file: string } + | { contents: string } + | Error + | null; -export type SassDoneFn = - (result: T) => void | T; +export type SassDoneFn = ( + result: T, +) => void | T; /** * @deprecated - Use types directly from `sass` package instead. */ -export type SassImporter = - (url: string, prev: string, done: SassDoneFn) => void | T; +export type SassImporter = ( + url: string, + prev: string, + done: SassDoneFn, +) => void | T; /** * @deprecated - Use types directly from `sass` package instead. */ export interface SassFunctionsObject { - [index: string]: types.Color | types.Number | types.String | types.List | types.Map | types.Null; + [index: string]: + | types.Color + | types.Number + | types.String + | types.List + | types.Map + | types.Null; } /** @@ -100,38 +118,38 @@ export type SassRenderResult = LegacyResult; * Rollup's `AssetInfo` bundle type. */ export interface RollupAssetInfo { - fileName: string, - name?: string, - source: string | Uint8Array, - type: 'asset', + fileName: string; + name?: string; + source: string | Uint8Array; + type: 'asset'; } /** * Rollup's `ChunkInfo` bundle type. */ export interface RollupChunkInfo { - code: string, - dynamicImports: string[], - exports: string[], - facadeModuleId: string | null, - fileName: string, - implicitlyLoadedBefore: string[], - imports: string[], - importedBindings: { [imported: string]: string[] }, - isDynamicEntry: boolean, - isEntry: boolean, - isImplicitEntry: boolean, - map: { [index: string]: string } | null, + code: string; + dynamicImports: string[]; + exports: string[]; + facadeModuleId: string | null; + fileName: string; + implicitlyLoadedBefore: string[]; + imports: string[]; + importedBindings: { [imported: string]: string[] }; + isDynamicEntry: boolean; + isEntry: boolean; + isImplicitEntry: boolean; + map: { [index: string]: string } | null; modules: { [id: string]: { - renderedExports: string[], - removedExports: string[], - renderedLength: number, - originalLength: number, - code: string | null - }, - }, - name: string, - referencedFiles: string[], - type: 'chunk', + renderedExports: string[]; + removedExports: string[]; + renderedLength: number; + originalLength: number; + code: string | null; + }; + }; + name: string; + referencedFiles: string[]; + type: 'chunk'; } diff --git a/src/utils/index.ts b/src/utils/index.ts index f9922ad..f128d2b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,14 +1,11 @@ -export const log = console.log.bind(console), // Binding here to make sure this works in older versions of node (v10 - v12 etc.). +export const log = console.log.bind(console); // Binding here to make sure this works in older versions of node (v10 - v12 etc.). +export const warn = console.warn.bind(console); // "" +export const error = console.error.bind(console); // "" - warn = console.warn.bind(console), // "" +export const isset = (x: unknown): boolean => x !== null && x !== undefined; - error = console.error.bind(console), // "" - - isset = (x: unknown): boolean => x !== null && x !== undefined, - - isString = (x: unknown): x is string => isset(x) && (x as object).constructor === String, - - isObject = (x: unknown): x is object => typeof x === 'object', - - isFunction = (x: unknown): x is Function => typeof x === 'function' -; +export const isString = (x: unknown): x is string => + isset(x) && (x as object).constructor === String; +export const isObject = (x: unknown): x is object => typeof x === 'object'; +export const isFunction = (x: unknown): x is Function => + typeof x === 'function'; diff --git a/test/index.test.ts b/test/index.test.ts index c27611e..6f9f9dc 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,26 +1,26 @@ -import test from "ava"; -import Sinon from "sinon"; -import { readFileSync, promises as fs, constants as fsConstants } from "fs"; -import * as path from "path"; -import { rollup } from "rollup"; +import test from 'ava'; +import Sinon from 'sinon'; +import { readFileSync, promises as fs, constants as fsConstants } from 'fs'; +import * as path from 'path'; +import { rollup } from 'rollup'; import type { OutputOptions, RollupOutput, WarningHandlerWithDefault, -} from "rollup"; -import * as sassRuntime from "sass"; -import postcss from "postcss"; -import { extractICSS } from "icss-utils"; +} from 'rollup'; +import * as sassRuntime from 'sass'; +import postcss from 'postcss'; +import { extractICSS } from 'icss-utils'; -import sass from "../src/index"; -import { RollupPluginSassOutputFn, SassOptions } from "../src/types"; +import sass from '../src/index'; +import { RollupPluginSassOutputFn, SassOptions } from '../src/types'; -const repoRoot = path.join(__dirname, "../"); +const repoRoot = path.join(__dirname, '../'); -const TEST_OUTPUT_DIR = path.join(repoRoot, ".tests-output/"); +const TEST_OUTPUT_DIR = path.join(repoRoot, '.tests-output/'); const TEST_SASS_OPTIONS_DEFAULT = { - outputStyle: "compressed", + outputStyle: 'compressed', } as SassOptions; const TEST_BASE_CONFIG = { @@ -32,54 +32,54 @@ const TEST_BASE_CONFIG = { }; const TEST_GENERATE_OPTIONS = { - format: "es", + format: 'es', } as OutputOptions; const TEST_OUTPUT_OPTIONS = { - format: "es", - file: path.join(TEST_OUTPUT_DIR, "bundle.js"), + format: 'es', + file: path.join(TEST_OUTPUT_DIR, 'bundle.js'), } as const; function stripNewLines(str: string): string { - return str.trim().replace(/[\n\r\f]/g, ""); + return str.trim().replace(/[\n\r\f]/g, ''); } -function getFirstChunkCode(outputChunks: RollupOutput["output"]): string { +function getFirstChunkCode(outputChunks: RollupOutput['output']): string { return outputChunks[0].code; } -test("should import *.scss and *.sass files", async (t) => { +test('should import *.scss and *.sass files', async (t) => { const outputBundle = await rollup({ - input: "test/fixtures/basic/index.js", + input: 'test/fixtures/basic/index.js', ...TEST_BASE_CONFIG, }); const { output } = await outputBundle.generate({ - format: "es", - file: path.join(TEST_OUTPUT_DIR, "import_scss_and_sass.js"), + format: 'es', + file: path.join(TEST_OUTPUT_DIR, 'import_scss_and_sass.js'), }); const result = getFirstChunkCode(output); t.snapshot(result); }); -test("should import *.scss and *.sass files with default configuration", async (t) => { +test('should import *.scss and *.sass files with default configuration', async (t) => { const outputBundle = await rollup({ - input: "test/fixtures/basic/index.js", + input: 'test/fixtures/basic/index.js', plugins: [sass()], }); const { output } = await outputBundle.generate({ ...TEST_GENERATE_OPTIONS, - file: path.join(TEST_OUTPUT_DIR, "import_scss_and_sass_default_options.js"), + file: path.join(TEST_OUTPUT_DIR, 'import_scss_and_sass_default_options.js'), }); const result = getFirstChunkCode(output); t.snapshot(result); }); -test("should compress the dest CSS", async (t) => { +test('should compress the dest CSS', async (t) => { const outputBundle = await rollup({ ...TEST_BASE_CONFIG, - input: "test/fixtures/compress/index.js", + input: 'test/fixtures/compress/index.js', }); const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); const result = getFirstChunkCode(output); @@ -87,9 +87,9 @@ test("should compress the dest CSS", async (t) => { t.snapshot(result); }); -test("should custom importer works", async (t) => { +test('should custom importer works', async (t) => { const outputBundle = await rollup({ - input: "test/fixtures/custom-importer/index.js", + input: 'test/fixtures/custom-importer/index.js', plugins: [ sass({ options: { @@ -97,7 +97,7 @@ test("should custom importer works", async (t) => { importer: [ (url, prev, done) => { done({ - file: url.replace("${name}", "actual_a"), + file: url.replace('${name}', 'actual_a'), }); }, ], @@ -111,14 +111,14 @@ test("should custom importer works", async (t) => { t.snapshot(result); }); -test("should support options.data", async (t) => { - const jsVars = { color_red: "red" }; +test('should support options.data', async (t) => { + const jsVars = { color_red: 'red' }; const scssVars = Object.entries(jsVars).reduce( (prev, [varName, varValue]) => `${prev}$${varName}:${varValue};`, - "" + '', ); const outputBundle = await rollup({ - input: "test/fixtures/data/index.js", + input: 'test/fixtures/data/index.js', plugins: [ sass({ options: { @@ -143,17 +143,17 @@ test("should support options.data", async (t) => { */ const onwarn: WarningHandlerWithDefault = (warning, defaultHandler) => { if ( - warning.code === "UNRESOLVED_IMPORT" && - warning.exporter?.includes("insertStyle.js") + warning.code === 'UNRESOLVED_IMPORT' && + warning.exporter?.includes('insertStyle.js') ) { return; } defaultHandler(warning); }; - test("should insert CSS into head tag", async (t) => { + test('should insert CSS into head tag', async (t) => { const outputBundle = await rollup({ - input: "test/fixtures/insert/index.js", + input: 'test/fixtures/insert/index.js', plugins: [ sass({ insert: true, @@ -168,11 +168,11 @@ test("should support options.data", async (t) => { t.snapshot(result); }); - test("should generate chunks with import insertStyle when `insert` is true", async (t) => { + test('should generate chunks with import insertStyle when `insert` is true', async (t) => { const outputBundle = await rollup({ input: { - entryA: "test/fixtures/multiple-entry-points/entryA.js", - entryB: "test/fixtures/multiple-entry-points/entryB.js", + entryA: 'test/fixtures/multiple-entry-points/entryA.js', + entryB: 'test/fixtures/multiple-entry-points/entryB.js', }, plugins: [ sass({ @@ -182,26 +182,26 @@ test("should support options.data", async (t) => { ], output: { preserveModules: true, - preserveModulesRoot: "src", + preserveModulesRoot: 'src', }, onwarn, }); const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); - t.is(output.length, 2, "has 2 chunks"); + t.is(output.length, 2, 'has 2 chunks'); t.true( output.every((outputItem) => { - if (outputItem.type === "chunk") { + if (outputItem.type === 'chunk') { const insertStyleImportsCount = outputItem.imports.filter((it) => - it.includes("/insertStyle.js") + it.includes('/insertStyle.js'), ).length; return insertStyleImportsCount === 1; } // if is an assets there is no need to check imports return true; }), - "each chunk must include insertStyle once" + 'each chunk must include insertStyle once', ); }); } @@ -210,17 +210,17 @@ test("should support options.data", async (t) => { // #region output option { const onwarn: WarningHandlerWithDefault = (warning, defaultHandler) => { - if (warning.code === "EMPTY_BUNDLE") { + if (warning.code === 'EMPTY_BUNDLE') { return; } defaultHandler(warning); }; const [expectA, expectB] = [ - "test/assets/expect_a.css", - "test/assets/expect_b.css", + 'test/assets/expect_a.css', + 'test/assets/expect_b.css', ].map((filePath) => - stripNewLines(readFileSync(filePath, { encoding: "utf-8" })) + stripNewLines(readFileSync(filePath, { encoding: 'utf-8' })), ); /** @@ -229,18 +229,18 @@ test("should support options.data", async (t) => { * Detailed information can be found here: https://github.com/elycruz/rollup-plugin-sass/pull/143#issuecomment-2227274405 */ - test("should support output as function", async (t) => { + test('should support output as function', async (t) => { const outputSpy = Sinon.spy() as Sinon.SinonSpy< Parameters, ReturnType >; const outputFilePath = path.join( TEST_OUTPUT_DIR, - "support_output_function.js" + 'support_output_function.js', ); const outputBundle = await rollup({ - input: "test/fixtures/output-function/index.js", + input: 'test/fixtures/output-function/index.js', plugins: [ sass({ output: outputSpy, @@ -255,12 +255,12 @@ test("should support options.data", async (t) => { const result = await fs .readFile(outputFilePath) .catch(() => - t.true(false, `Trouble reading back written file "${outputFilePath}".`) + t.true(false, `Trouble reading back written file "${outputFilePath}".`), ); - t.is(stripNewLines(result.toString()), "", "JS bundle mus be empty"); + t.is(stripNewLines(result.toString()), '', 'JS bundle mus be empty'); - t.true(outputSpy.calledOnce, "output function has been called"); + t.true(outputSpy.calledOnce, 'output function has been called'); const [actualCSSstring, ...otherOutputParams] = outputSpy.args[0]; @@ -270,16 +270,16 @@ test("should support options.data", async (t) => { [ `expect "${actualCSSstring}" to include "${expectedChunkCSS}"`, `Additional params are: ${JSON.stringify(otherOutputParams)}`, - ].join("\n") + ].join('\n'), ); }); }); - test("should support output as (non-previously existent) path", async (t) => { - const outputStylePath = path.join(TEST_OUTPUT_DIR, "output-path/style.css"); + test('should support output as (non-previously existent) path', async (t) => { + const outputStylePath = path.join(TEST_OUTPUT_DIR, 'output-path/style.css'); const outputBundle = await rollup({ - input: "test/fixtures/output-path/index.js", + input: 'test/fixtures/output-path/index.js', plugins: [ sass({ output: outputStylePath, @@ -291,21 +291,21 @@ test("should support options.data", async (t) => { const outputFilePath = path.join( TEST_OUTPUT_DIR, - "support_output_prev-non-exist.js" + 'support_output_prev-non-exist.js', ); await outputBundle.write({ ...TEST_OUTPUT_OPTIONS, file: outputFilePath }); const outputFileContent = await fs.readFile(outputFilePath); - t.is(stripNewLines(outputFileContent.toString()), ""); + t.is(stripNewLines(outputFileContent.toString()), ''); const outputStyleContent = await fs.readFile(outputStylePath); t.true(stripNewLines(outputStyleContent.toString()).includes(expectA)); t.true(stripNewLines(outputStyleContent.toString()).includes(expectB)); }); - test("should support output as true", async (t) => { + test('should support output as true', async (t) => { const outputBundle = await rollup({ - input: "test/fixtures/output-true/index.js", + input: 'test/fixtures/output-true/index.js', plugins: [ sass({ output: true, @@ -317,14 +317,14 @@ test("should support options.data", async (t) => { const outputFilePath = path.join( TEST_OUTPUT_DIR, - "support-output-as-true.js" + 'support-output-as-true.js', ); await outputBundle.write({ ...TEST_OUTPUT_OPTIONS, file: outputFilePath }); const outputFileContent = await fs.readFile(outputFilePath); - t.is(stripNewLines(outputFileContent.toString()), ""); + t.is(stripNewLines(outputFileContent.toString()), ''); - const outputStylePath = outputFilePath.replace(".js", ".css"); + const outputStylePath = outputFilePath.replace('.js', '.css'); const outputStyleContent = await fs.readFile(outputStylePath); t.true(outputStyleContent.toString().includes(expectA)); t.true(outputStyleContent.toString().includes(expectB)); @@ -333,11 +333,11 @@ test("should support options.data", async (t) => { // #endregion // #region processor option -test("should processor return as string", async (t) => { - const reverse = (str: string): string => str.split("").reverse().join(""); +test('should processor return as string', async (t) => { + const reverse = (str: string): string => str.split('').reverse().join(''); const outputBundle = await rollup({ - input: "test/fixtures/processor-string/index.js", + input: 'test/fixtures/processor-string/index.js', plugins: [ sass({ processor: (css) => reverse(css), @@ -351,15 +351,15 @@ test("should processor return as string", async (t) => { t.snapshot(result); }); -test("should processor return as object", async (t) => { +test('should processor return as object', async (t) => { const outputBundle = await rollup({ - input: "test/fixtures/processor-object/index.js", + input: 'test/fixtures/processor-object/index.js', plugins: [ sass({ processor: (css) => ({ css, - foo: "foo", - bar: "bar", + foo: 'foo', + bar: 'bar', }), options: TEST_SASS_OPTIONS_DEFAULT, }), @@ -371,9 +371,9 @@ test("should processor return as object", async (t) => { t.snapshot(result); }); -test("should support processor return type `Promise`", async (t) => { +test('should support processor return type `Promise`', async (t) => { const outputBundle = await rollup({ - input: "test/fixtures/processor-promise/index.js", + input: 'test/fixtures/processor-promise/index.js', plugins: [ sass({ processor: (css) => @@ -388,9 +388,9 @@ test("should support processor return type `Promise`", async (t) => { t.snapshot(result); }); -test("should support processor return type `Promise<{css: string, icssExport: {}, icssImport: {}}}>", async (t) => { +test('should support processor return type `Promise<{css: string, icssExport: {}, icssImport: {}}}>', async (t) => { const outputBundle = await rollup({ - input: "test/fixtures/processor-promise/with-icss-exports.js", + input: 'test/fixtures/processor-promise/with-icss-exports.js', plugins: [ sass({ processor: (css) => { @@ -415,30 +415,30 @@ test("should support processor return type `Promise<{css: string, icssExport: {} t.snapshot(result); }); -test("should throw an error when processor returns an object type missing the `css` prop.", async (t) => { +test('should throw an error when processor returns an object type missing the `css` prop.', async (t) => { const sassPlugin = sass({ // @ts-expect-error - Ignoring incorrect type (for test). processor: () => ({}), options: TEST_SASS_OPTIONS_DEFAULT, }); const message = - "You need to return the styles using the `css` property. See https://github.com/differui/rollup-plugin-sass#processor"; + 'You need to return the styles using the `css` property. See https://github.com/differui/rollup-plugin-sass#processor'; await t.throwsAsync( () => rollup({ - input: "test/fixtures/processor-error/index.js", + input: 'test/fixtures/processor-error/index.js', plugins: [sassPlugin], }), - { message } + { message }, ); }); // #endregion // #region node resolution -test("should resolve ~ as node_modules", async (t) => { +test('should resolve ~ as node_modules', async (t) => { const outputBundle = await rollup({ - input: "test/fixtures/import/index.js", + input: 'test/fixtures/import/index.js', plugins: [ sass({ options: TEST_SASS_OPTIONS_DEFAULT, @@ -451,18 +451,18 @@ test("should resolve ~ as node_modules", async (t) => { t.snapshot(result); }); -test("should resolve ~ as node_modules and output javascript modules", async (t) => { +test('should resolve ~ as node_modules and output javascript modules', async (t) => { const outputFilePathES = path.join( TEST_OUTPUT_DIR, - "import_scss_and_sass.es.js" + 'import_scss_and_sass.es.js', ); const outputFilePathCJS = path.join( TEST_OUTPUT_DIR, - "import_scss_and_sass.cjs.js" + 'import_scss_and_sass.cjs.js', ); const outputBundle = await rollup({ - input: "test/fixtures/import/index.js", + input: 'test/fixtures/import/index.js', plugins: [ sass({ options: TEST_SASS_OPTIONS_DEFAULT, @@ -472,39 +472,39 @@ test("should resolve ~ as node_modules and output javascript modules", async (t) const { output } = await outputBundle.generate(TEST_GENERATE_OPTIONS); const result = getFirstChunkCode(output); - t.snapshot(result, "check output result code"); + t.snapshot(result, 'check output result code'); // Test 'es' module output // ---- await outputBundle.write({ - format: "es", + format: 'es', file: outputFilePathES, }); const esFileContent = await fs.readFile(outputFilePathES); t.snapshot( esFileContent.toString(), - "Ensure content exist in ESM output file" + 'Ensure content exist in ESM output file', ); // Test 'cjs' module output // ---- await outputBundle.write({ - format: "cjs", + format: 'cjs', file: outputFilePathCJS, }); const cjsFileContent = await fs.readFile(outputFilePathCJS); t.snapshot( cjsFileContent.toString(), - "Ensure content exist in CJS output file" + 'Ensure content exist in CJS output file', ); }); // #endregion -test("should support options.runtime", async (t) => { +test('should support options.runtime', async (t) => { const outputBundle = await rollup({ - input: "test/fixtures/runtime/index.js", + input: 'test/fixtures/runtime/index.js', plugins: [ sass({ runtime: sassRuntime, @@ -522,11 +522,11 @@ test("should support options.runtime", async (t) => { test("When `sourcemap` isn't set adjacent source map files should not be output, and rollup output chunk shouldn't contain a `map` entry", async (t) => { const outputFilePath = path.join( TEST_OUTPUT_DIR, - "with-no-adjacent-source-map.js" + 'with-no-adjacent-source-map.js', ); const bundle = await rollup({ - input: "test/fixtures/basic/index.js", + input: 'test/fixtures/basic/index.js', plugins: [ sass({ options: TEST_SASS_OPTIONS_DEFAULT, @@ -538,34 +538,34 @@ test("When `sourcemap` isn't set adjacent source map files should not be output, // Run test const writeResult = await bundle.write({ file: outputFilePath, - format: "esm", + format: 'esm', }); const writeResultOutput = writeResult.output; // Check for output chunk - t.true(!!writeResult.output?.length, "output should contain an output chunk"); + t.true(!!writeResult.output?.length, 'output should contain an output chunk'); // Check absence of 'map' entry in chunk t.falsy( writeResultOutput[0].map, - "output chunk's `map` property should not be set. It should equal `null` or `undefined`" + "output chunk's `map` property should not be set. It should equal `null` or `undefined`", ); // Check for absence of source map file await fs.access(sourceMapFilePath, fsConstants.R_OK).then( () => t.false(true, `'${sourceMapFilePath}' should not exist.`), - () => t.true(true) + () => t.true(true), ); }); -test("When `sourcemap` is set, to `true`, adjacent source map file should be output, and rollup output chunk should contain `map` entry", async (t) => { +test('When `sourcemap` is set, to `true`, adjacent source map file should be output, and rollup output chunk should contain `map` entry', async (t) => { const outputFilePath = path.join( TEST_OUTPUT_DIR, - "with-adjacent-source-map.js" + 'with-adjacent-source-map.js', ); const bundle = await rollup({ - input: "test/fixtures/basic/index.js", + input: 'test/fixtures/basic/index.js', plugins: [ sass({ options: TEST_SASS_OPTIONS_DEFAULT, @@ -577,16 +577,16 @@ test("When `sourcemap` is set, to `true`, adjacent source map file should be out const writeResult = await bundle.write({ file: outputFilePath, sourcemap: true, - format: "esm", + format: 'esm', }); // Check for output chunk - t.true(!!writeResult.output?.length, "output should contain an output chunk"); + t.true(!!writeResult.output?.length, 'output should contain an output chunk'); // Check for 'map' entry in chunk t.true( !!writeResult.output[0].map, - "rollup output output chunk's `map` property should be set" + "rollup output output chunk's `map` property should be set", ); // Check for source map file @@ -594,13 +594,13 @@ test("When `sourcemap` is set, to `true`, adjacent source map file should be out t.true( !!contents.toString(), - `${sourceMapFilePath} should have been written.` + `${sourceMapFilePath} should have been written.`, ); }); // #endregion -test("module stylesheets graph should be added to watch list", async (t) => { - const inputFilePath = "test/fixtures/dependencies/index.js"; +test('module stylesheets graph should be added to watch list', async (t) => { + const inputFilePath = 'test/fixtures/dependencies/index.js'; // Bundle our dependencies fixture module // ---- @@ -616,16 +616,16 @@ test("module stylesheets graph should be added to watch list", async (t) => { // List of nested stylesheets paths // ---- const nestedFilePaths = [ - "test/fixtures/dependencies/style1.scss", - "test/fixtures/dependencies/empty-style1.scss", - "test/fixtures/dependencies/style2.sass", - "test/fixtures/dependencies/style3.scss", - "test/fixtures/dependencies/empty-style3.scss", - "test/fixtures/dependencies/empty-style2.sass", + 'test/fixtures/dependencies/style1.scss', + 'test/fixtures/dependencies/empty-style1.scss', + 'test/fixtures/dependencies/style2.sass', + 'test/fixtures/dependencies/style3.scss', + 'test/fixtures/dependencies/empty-style3.scss', + 'test/fixtures/dependencies/empty-style2.sass', ]; const expectedWatchedFiles = [ - "test/fixtures/dependencies/index.js", + 'test/fixtures/dependencies/index.js', ...nestedFilePaths, ]; @@ -636,13 +636,13 @@ test("module stylesheets graph should be added to watch list", async (t) => { t.deepEqual( bundle.watchFiles.length, expectedWatchedFiles.length, - 'should contain expected number of "watched" files' + 'should contain expected number of "watched" files', ); // Ensure our initial 'index.js' module is being watched t.true( bundle.watchFiles[0].endsWith(inputFilePath), - 'Expected `bundle.watchFiles[0]` to end with "index.js"' + 'Expected `bundle.watchFiles[0]` to end with "index.js"', ); // Ensure 'index.js' module, and other files in dep tree are watched @@ -650,28 +650,28 @@ test("module stylesheets graph should be added to watch list", async (t) => { const expectedTail = expectedWatchedFiles[i]; t.true( filePath.endsWith(expectedTail), - `${filePath} should end with ${expectedTail}` + `${filePath} should end with ${expectedTail}`, ); }); // Get target module. // ---- const targetModule = bundle?.cache?.modules[0]!; - t.true(!!targetModule, "Expected bundle data"); + t.true(!!targetModule, 'Expected bundle data'); // Ensure target module transform dependencies indeed end with expected file path tails. // ---- t.true( targetModule.transformDependencies?.every( - (filePath) => !!expectedWatchedFiles.find((fp) => filePath.endsWith(fp)) + (filePath) => !!expectedWatchedFiles.find((fp) => filePath.endsWith(fp)), ), - "`bundle.cache.modules[0].transformDependencies` entries should each end with expected file-path tails" + '`bundle.cache.modules[0].transformDependencies` entries should each end with expected file-path tails', ); // Test final content output // ---- const expectedFinalContent = await fs.readFile( - "test/fixtures/dependencies/expected.js" + 'test/fixtures/dependencies/expected.js', ); t.is(targetModule.code.trim(), expectedFinalContent.toString().trim()); }); diff --git a/test/insertStyle.test.ts b/test/insertStyle.test.ts index 8ef4a4d..6b8c413 100644 --- a/test/insertStyle.test.ts +++ b/test/insertStyle.test.ts @@ -1,22 +1,22 @@ -import test from "ava"; -import insertStyle from "../src/insertStyle"; -import { Browser } from "happy-dom"; -import Sinon from "sinon"; +import test from 'ava'; +import insertStyle from '../src/insertStyle'; +import { Browser } from 'happy-dom'; +import Sinon from 'sinon'; -const expectA = "body{color:red}"; +const expectA = 'body{color:red}'; -test("should insertStyle works", async (t) => { +test('should insertStyle works', async (t) => { const browser = new Browser(); const page = browser.newPage(); - page.url = "https://example.com"; + page.url = 'https://example.com'; page.content = ``; // --- // use Sinon fake to augment the global scope with window and document from the happy dom page - Sinon.define(global, "window", page.mainFrame.window); - Sinon.define(global, "document", page.mainFrame.window.document); + Sinon.define(global, 'window', page.mainFrame.window); + Sinon.define(global, 'document', page.mainFrame.window.document); // --- // Remove overrides @@ -29,21 +29,21 @@ test("should insertStyle works", async (t) => { const cssStr = insertStyle(expectA); - const styleSheet = document.head.querySelector("style")!; + const styleSheet = document.head.querySelector('style')!; t.is( styleSheet.textContent, cssStr!, - "stylesheet's content should equal returned css string" + "stylesheet's content should equal returned css string", ); t.is( styleSheet.type, - "text/css", - 'Should contain `type` attrib. equal to "text/css"' + 'text/css', + 'Should contain `type` attrib. equal to "text/css"', ); await browser.close(); }); test("insertStyle shouldn't choke when window is undefined", (t) => { - t.notThrows(() => insertStyle("css")); + t.notThrows(() => insertStyle('css')); }); diff --git a/test/rollup.config.test.ts b/test/rollup.config.test.ts new file mode 100644 index 0000000..4bd0ddc --- /dev/null +++ b/test/rollup.config.test.ts @@ -0,0 +1,6 @@ +// Should pass `tsc` dry-run (`tsc ... --noEmit`) with no errors. +// (Since this file is used only to test types it is excluded from ava execution) + +import sass from '../src/index'; + +const instance = sass(); diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 5e7a1d8..7776d72 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -1,14 +1,7 @@ { "extends": "./tsconfig.json", + "include": ["./test/*"], "compilerOptions": { - "allowJs": true, - "esModuleInterop": true - }, - "include": [ - "./test/*" - ], - "exclude": [ - "**/node_modules/**" - ] + "allowJs": true + } } -