From 357615854a0a4386ea702d24b712eed0b74560f9 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 9 Jun 2023 17:26:48 -0700 Subject: [PATCH] Emit a deprecation warning when loaded as a default export See #229 --- .github/workflows/ci.yml | 2 + lib/index.mjs | 134 ++++++++++++++++++++++++++++++++++++ package.json | 7 +- test/after-compile-test.mjs | 33 +++++++++ test/dependencies.test.ts | 1 - tool/prepare-release.ts | 1 + 6 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 lib/index.mjs create mode 100644 test/after-compile-test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58ec7fd9..58d4182f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,6 +72,8 @@ jobs: npm run init -- --compiler-path=dart-sass --language-path=language $args - run: npm run test + - run: npm run compile + - run: node test/after-compile-test.mjs # The versions should be kept up-to-date with the latest LTS Node releases. # They next need to be rotated October 2021. See diff --git a/lib/index.mjs b/lib/index.mjs new file mode 100644 index 00000000..db85256e --- /dev/null +++ b/lib/index.mjs @@ -0,0 +1,134 @@ +import * as sass from './index.js'; + +export const compile = sass.compile; +export const compileAsync = sass.compileAsync; +export const compileString = sass.compileString; +export const compileStringAsync = sass.compileStringAsync; +export const Logger = sass.Logger; +export const SassArgumentList = sass.SassArgumentList; +export const SassBoolean = sass.SassBoolean; +export const SassColor = sass.SassColor; +export const SassFunction = sass.SassFunction; +export const SassList = sass.SassList; +export const SassMap = sass.SassMap; +export const SassNumber = sass.SassNumber; +export const SassString = sass.SassString; +export const Value = sass.Value; +export const CustomFunction = sass.CustomFunction; +export const ListSeparator = sass.ListSeparator; +export const sassFalse = sass.sassFalse; +export const sassNull = sass.sassNull; +export const sassTrue = sass.sassTrue; +export const Exception = sass.Exception; +export const PromiseOr = sass.PromiseOr; +export const info = sass.info; +export const render = sass.render; +export const renderSync = sass.renderSync; + +let printedDefaultExportDeprecation = false; +function defaultExportDeprecation() { + if (printedDefaultExportDeprecation) return; + printedDefaultExportDeprecation = true; + console.error( + "`import sass from 'sass'` is deprecated.\n" + + "Please use `import * as sass from 'sass'` instead."); +} + +export default { + get compile() { + defaultExportDeprecation(); + return sass.compile; + }, + get compileAsync() { + defaultExportDeprecation(); + return sass.compileAsync; + }, + get compileString() { + defaultExportDeprecation(); + return sass.compileString; + }, + get compileStringAsync() { + defaultExportDeprecation(); + return sass.compileStringAsync; + }, + get Logger() { + defaultExportDeprecation(); + return sass.Logger; + }, + get SassArgumentList() { + defaultExportDeprecation(); + return sass.SassArgumentList; + }, + get SassBoolean() { + defaultExportDeprecation(); + return sass.SassBoolean; + }, + get SassColor() { + defaultExportDeprecation(); + return sass.SassColor; + }, + get SassFunction() { + defaultExportDeprecation(); + return sass.SassFunction; + }, + get SassList() { + defaultExportDeprecation(); + return sass.SassList; + }, + get SassMap() { + defaultExportDeprecation(); + return sass.SassMap; + }, + get SassNumber() { + defaultExportDeprecation(); + return sass.SassNumber; + }, + get SassString() { + defaultExportDeprecation(); + return sass.SassString; + }, + get Value() { + defaultExportDeprecation(); + return sass.Value; + }, + get CustomFunction() { + defaultExportDeprecation(); + return sass.CustomFunction; + }, + get ListSeparator() { + defaultExportDeprecation(); + return sass.ListSeparator; + }, + get sassFalse() { + defaultExportDeprecation(); + return sass.sassFalse; + }, + get sassNull() { + defaultExportDeprecation(); + return sass.sassNull; + }, + get sassTrue() { + defaultExportDeprecation(); + return sass.sassTrue; + }, + get Exception() { + defaultExportDeprecation(); + return sass.Exception; + }, + get PromiseOr() { + defaultExportDeprecation(); + return sass.PromiseOr; + }, + get info() { + defaultExportDeprecation(); + return sass.info; + }, + get render() { + defaultExportDeprecation(); + return sass.render; + }, + get renderSync() { + defaultExportDeprecation(); + return sass.renderSync; + }, +}; diff --git a/package.json b/package.json index 4d39dd78..c5a7e7d3 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,10 @@ "repository": "sass/embedded-host-node", "author": "Google Inc.", "license": "MIT", - "main": "dist/lib/index.js", + "exports": { + "import": "dist/lib/index.mjs", + "default": "dist/lib/index.js" + }, "types": "dist/types/index.d.ts", "files": [ "dist/**/*" @@ -21,7 +24,7 @@ "check:gts": "gts check", "check:tsc": "tsc --noEmit", "clean": "gts clean", - "compile": "tsc", + "compile": "tsc && cp lib/index.mjs dist/lib/index.mjs", "fix": "gts fix", "prepublishOnly": "npm run clean && ts-node ./tool/prepare-release.ts", "test": "jest" diff --git a/test/after-compile-test.mjs b/test/after-compile-test.mjs new file mode 100644 index 00000000..805cbca6 --- /dev/null +++ b/test/after-compile-test.mjs @@ -0,0 +1,33 @@ +// Copyright 2023 Google LLC. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import * as fs from 'fs'; + +// Note: this file isn't .test.ts specifically because we _don't_ want Jest to +// handle it, because Jest chokes on dynamic imports of literal ESM modules. + +// This file should only be run _after_ `npm run compile`. +if (!fs.existsSync('dist/package.json')) { + throw new Error('after-compile.test.ts must be run after `npm run compile`.'); +} + +// Load these dynamically so we have a better error mesage if `npm run compile` +// hasn't been run. +const cjs = await import('../dist/lib/index.js'); +const esm = await import('../dist/lib/index.mjs'); + +for (const [name, value] of Object.entries(cjs)) { + if (name === '__esModule' || name === 'default') continue; + if (!esm[name]) { + throw new Error(`ESM module is missing export ${name}.`); + } else if (esm[name] !== value) { + throw new Error(`ESM ${name} isn't the same as CJS.`); + } + + if (!esm.default[name]) { + throw new Error(`ESM default export is missing export ${name}.`); + } else if (esm.default[name] !== value) { + throw new Error(`ESM default export ${name} isn't the same as CJS.`); + } +} diff --git a/test/dependencies.test.ts b/test/dependencies.test.ts index 25b3bea6..e29f547a 100644 --- a/test/dependencies.test.ts +++ b/test/dependencies.test.ts @@ -2,7 +2,6 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -import * as child_process from 'child_process'; import * as fs from 'fs'; import * as p from 'path'; diff --git a/tool/prepare-release.ts b/tool/prepare-release.ts index cc741c21..8d4c08b4 100644 --- a/tool/prepare-release.ts +++ b/tool/prepare-release.ts @@ -16,6 +16,7 @@ import {getLanguageRepo} from './get-language-repo'; console.log('Transpiling TS into dist.'); shell.exec('tsc'); + shell.cp('lib/index.mjs', 'dist/lib/index.mjs'); console.log('Copying JS API types to dist.'); shell.cp('-R', 'lib/src/vendor/sass', 'dist/types');