From 27034ecdf74b8d219a919df97d1f6d481039386e Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Sat, 30 Apr 2022 21:29:12 +0200 Subject: [PATCH 1/3] Pass targets for swc/helpers to Rust and only filter them for preset env (#8020) --- .../js-export-arrow-support/bar/index.js | 1 + .../js-export-arrow-support/bar/other.js | 1 + .../js-export-arrow-support/index.js | 5 +++ .../node_modules/foo/index.js | 1 + .../node_modules/foo/other.js | 1 + .../node_modules/foo/package.json | 4 ++ .../js-export-arrow-support/package.json | 3 ++ .../js-export-arrow-support/yarn.lock | 0 .../core/integration-tests/test/javascript.js | 8 ++++ packages/transformers/js/core/src/lib.rs | 37 ++++++++++++------- packages/transformers/js/src/JSTransformer.js | 6 +-- 11 files changed, 48 insertions(+), 19 deletions(-) create mode 100644 packages/core/integration-tests/test/integration/js-export-arrow-support/bar/index.js create mode 100644 packages/core/integration-tests/test/integration/js-export-arrow-support/bar/other.js create mode 100644 packages/core/integration-tests/test/integration/js-export-arrow-support/index.js create mode 100644 packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/index.js create mode 100644 packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/other.js create mode 100644 packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/package.json create mode 100644 packages/core/integration-tests/test/integration/js-export-arrow-support/package.json create mode 100644 packages/core/integration-tests/test/integration/js-export-arrow-support/yarn.lock diff --git a/packages/core/integration-tests/test/integration/js-export-arrow-support/bar/index.js b/packages/core/integration-tests/test/integration/js-export-arrow-support/bar/index.js new file mode 100644 index 00000000000..4a3e08d7cac --- /dev/null +++ b/packages/core/integration-tests/test/integration/js-export-arrow-support/bar/index.js @@ -0,0 +1 @@ +export {bar} from "./other.js"; diff --git a/packages/core/integration-tests/test/integration/js-export-arrow-support/bar/other.js b/packages/core/integration-tests/test/integration/js-export-arrow-support/bar/other.js new file mode 100644 index 00000000000..c399d568960 --- /dev/null +++ b/packages/core/integration-tests/test/integration/js-export-arrow-support/bar/other.js @@ -0,0 +1 @@ +export const bar = "bar"; diff --git a/packages/core/integration-tests/test/integration/js-export-arrow-support/index.js b/packages/core/integration-tests/test/integration/js-export-arrow-support/index.js new file mode 100644 index 00000000000..bc46dfad6b5 --- /dev/null +++ b/packages/core/integration-tests/test/integration/js-export-arrow-support/index.js @@ -0,0 +1,5 @@ +import * as helpers from "@swc/helpers"; +import * as bar from "./bar"; +import * as foo from "foo"; + +export default [helpers, bar, foo]; diff --git a/packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/index.js b/packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/index.js new file mode 100644 index 00000000000..a0055229b0c --- /dev/null +++ b/packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/index.js @@ -0,0 +1 @@ +export {foo} from "./other.js"; diff --git a/packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/other.js b/packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/other.js new file mode 100644 index 00000000000..61d366eb252 --- /dev/null +++ b/packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/other.js @@ -0,0 +1 @@ +export const foo = "foo"; diff --git a/packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/package.json b/packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/package.json new file mode 100644 index 00000000000..1aea4910222 --- /dev/null +++ b/packages/core/integration-tests/test/integration/js-export-arrow-support/node_modules/foo/package.json @@ -0,0 +1,4 @@ +{ + "name": "foo", + "version": "0.0.0" +} diff --git a/packages/core/integration-tests/test/integration/js-export-arrow-support/package.json b/packages/core/integration-tests/test/integration/js-export-arrow-support/package.json new file mode 100644 index 00000000000..2a21928019b --- /dev/null +++ b/packages/core/integration-tests/test/integration/js-export-arrow-support/package.json @@ -0,0 +1,3 @@ +{ + "browserslist": "ie 11" +} diff --git a/packages/core/integration-tests/test/integration/js-export-arrow-support/yarn.lock b/packages/core/integration-tests/test/integration/js-export-arrow-support/yarn.lock new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/javascript.js b/packages/core/integration-tests/test/javascript.js index ff23790cba6..2ca75a87198 100644 --- a/packages/core/integration-tests/test/javascript.js +++ b/packages/core/integration-tests/test/javascript.js @@ -5110,6 +5110,14 @@ describe('javascript', function () { assert.deepEqual(res, {a: 4}); }); + it('should not use arrow functions for reexport declarations unless supported', async function () { + let b = await bundle( + path.join(__dirname, 'integration/js-export-arrow-support/index.js'), + ); + let content = await outputFS.readFile(b.getBundles()[0].filePath, 'utf8'); + assert(!content.includes('=>')); + }); + it('should support import namespace declarations of other ES modules', async function () { let b = await bundle( path.join(__dirname, 'integration/js-import-namespace/a.js'), diff --git a/packages/transformers/js/core/src/lib.rs b/packages/transformers/js/core/src/lib.rs index 72eca854405..0747b4e67ce 100644 --- a/packages/transformers/js/core/src/lib.rs +++ b/packages/transformers/js/core/src/lib.rs @@ -91,6 +91,7 @@ pub struct Config { is_library: bool, is_esm_output: bool, trace_bailouts: bool, + is_swc_helpers: bool, } #[derive(Serialize, Debug, Default)] @@ -318,11 +319,16 @@ pub fn transform(config: Config) -> Result { ..Default::default() }; let versions = targets_to_versions(&config.targets); - if let Some(versions) = versions { - preset_env_config.targets = Some(Targets::Versions(versions)); - preset_env_config.shipped_proposals = true; - preset_env_config.mode = Some(Entry); - preset_env_config.bugfixes = true; + if !config.is_swc_helpers { + // Avoid transpiling @swc/helpers so that we don't cause infinite recursion. + // Filter the versions for preset_env only so that syntax support checks + // (e.g. in esm2cjs) still work correctly. + if let Some(versions) = versions { + preset_env_config.targets = Some(Targets::Versions(versions)); + preset_env_config.shipped_proposals = true; + preset_env_config.mode = Some(Entry); + preset_env_config.bugfixes = true; + } } let mut diagnostics = vec![]; @@ -384,15 +390,18 @@ pub fn transform(config: Config) -> Result { config.insert_node_globals && config.source_type != SourceType::Script ), // Transpile new syntax to older syntax if needed - Optional::new( - preset_env( - global_mark, - Some(&comments), - preset_env_config, - Default::default() - ), - config.targets.is_some() - ), + { + let should_transpile = preset_env_config.targets.is_some(); + Optional::new( + preset_env( + global_mark, + Some(&comments), + preset_env_config, + Default::default(), + ), + should_transpile, + ) + }, // Inject SWC helpers if needed. helpers::inject_helpers(), ); diff --git a/packages/transformers/js/src/JSTransformer.js b/packages/transformers/js/src/JSTransformer.js index fdd954d0405..c8cbf426f4e 100644 --- a/packages/transformers/js/src/JSTransformer.js +++ b/packages/transformers/js/src/JSTransformer.js @@ -334,11 +334,6 @@ export default (new Transformer({ targets = {node: semver.minVersion(asset.env.engines.node)?.toString()}; } - // Avoid transpiling @swc/helpers so that we don't cause infinite recursion. - if (/@swc[/\\]helpers/.test(asset.filePath)) { - targets = null; - } - let env: EnvMap = {}; if (!config?.inlineEnvironment) { @@ -420,6 +415,7 @@ export default (new Transformer({ is_library: asset.env.isLibrary, is_esm_output: asset.env.outputFormat === 'esmodule', trace_bailouts: options.logLevel === 'verbose', + is_swc_helpers: /@swc[/\\]helpers/.test(asset.filePath), }); let convertLoc = loc => { From 0c9d93fd46deff683e31343d53b8d32871bbf630 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Sun, 1 May 2022 17:46:37 +0200 Subject: [PATCH 2/3] Correctly emit warnings for unnecessary PostCSS plugins in package.json (#8024) --- .../postcss-modules-config-package/foo.css | 3 ++ .../postcss-modules-config-package/foo.js | 5 +++ .../postcss-modules-config-package/index.css | 3 ++ .../postcss-modules-config-package/index.js | 6 ++++ .../package.json | 10 ++++++ .../postcss-modules-config-package/yarn.lock | 0 .../core/integration-tests/test/postcss.js | 31 +++++++++++++++++++ .../postcss/src/PostCSSTransformer.js | 3 ++ .../transformers/postcss/src/loadConfig.js | 6 ++-- 9 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 packages/core/integration-tests/test/integration/postcss-modules-config-package/foo.css create mode 100644 packages/core/integration-tests/test/integration/postcss-modules-config-package/foo.js create mode 100644 packages/core/integration-tests/test/integration/postcss-modules-config-package/index.css create mode 100644 packages/core/integration-tests/test/integration/postcss-modules-config-package/index.js create mode 100644 packages/core/integration-tests/test/integration/postcss-modules-config-package/package.json create mode 100644 packages/core/integration-tests/test/integration/postcss-modules-config-package/yarn.lock diff --git a/packages/core/integration-tests/test/integration/postcss-modules-config-package/foo.css b/packages/core/integration-tests/test/integration/postcss-modules-config-package/foo.css new file mode 100644 index 00000000000..f005d67ab91 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-modules-config-package/foo.css @@ -0,0 +1,3 @@ +.foo { + color: #fff; +} diff --git a/packages/core/integration-tests/test/integration/postcss-modules-config-package/foo.js b/packages/core/integration-tests/test/integration/postcss-modules-config-package/foo.js new file mode 100644 index 00000000000..c76d83b8490 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-modules-config-package/foo.js @@ -0,0 +1,5 @@ +const map = require('./foo.css'); + +module.exports = { + foo: map.foo, +}; diff --git a/packages/core/integration-tests/test/integration/postcss-modules-config-package/index.css b/packages/core/integration-tests/test/integration/postcss-modules-config-package/index.css new file mode 100644 index 00000000000..ae844dcf78b --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-modules-config-package/index.css @@ -0,0 +1,3 @@ +body { + background: blue; +} diff --git a/packages/core/integration-tests/test/integration/postcss-modules-config-package/index.js b/packages/core/integration-tests/test/integration/postcss-modules-config-package/index.js new file mode 100644 index 00000000000..0807176f23f --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-modules-config-package/index.js @@ -0,0 +1,6 @@ +require('./index.css'); +var foo = require('./foo'); + +module.exports = function () { + return foo.foo; +}; diff --git a/packages/core/integration-tests/test/integration/postcss-modules-config-package/package.json b/packages/core/integration-tests/test/integration/postcss-modules-config-package/package.json new file mode 100644 index 00000000000..c00e927214c --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-modules-config-package/package.json @@ -0,0 +1,10 @@ +{ + "postcss": { + "modules": true, + "plugins": { + "autoprefixer": { + "grid": true + } + } + } +} diff --git a/packages/core/integration-tests/test/integration/postcss-modules-config-package/yarn.lock b/packages/core/integration-tests/test/integration/postcss-modules-config-package/yarn.lock new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/postcss.js b/packages/core/integration-tests/test/postcss.js index 877f64be089..322d34ce89c 100644 --- a/packages/core/integration-tests/test/postcss.js +++ b/packages/core/integration-tests/test/postcss.js @@ -46,6 +46,37 @@ describe('postcss', () => { assert(css.includes(`.${cssClass}`)); }); + it('should build successfully with only postcss-modules config in package.json', async () => { + let b = await bundle( + path.join( + __dirname, + '/integration/postcss-modules-config-package/index.js', + ), + ); + + assertBundles(b, [ + { + name: 'index.js', + assets: ['foo.css', 'foo.js', 'index.css', 'index.js'], + }, + { + name: 'index.css', + assets: ['foo.css', 'index.css'], + }, + ]); + + let output = await run(b); + assert.equal(typeof output, 'function'); + + let value = output(); + assert(/foo_[0-9a-z]/.test(value)); + + let cssClass = value.match(/(foo_[0-9a-z])/)[1]; + + let css = await outputFS.readFile(path.join(distDir, 'index.css'), 'utf8'); + assert(css.includes(`.${cssClass}`)); + }); + it('should support transforming with postcss twice with the same result', async () => { let b = await bundle( path.join(__dirname, '/integration/postcss-plugins/index.js'), diff --git a/packages/transformers/postcss/src/PostCSSTransformer.js b/packages/transformers/postcss/src/PostCSSTransformer.js index d26cfe6444d..cc700a16215 100644 --- a/packages/transformers/postcss/src/PostCSSTransformer.js +++ b/packages/transformers/postcss/src/PostCSSTransformer.js @@ -101,6 +101,9 @@ export default (new Transformer({ configKey = '/plugins/postcss-modules'; hint = md`Remove the "postcss-modules" plugin from __${filename}__`; } + if (filename === 'package.json') { + configKey = `/postcss${configKey}`; + } let hints = [ 'Enable the "cssModules" option for "@parcel/transformer-css" in your package.json', diff --git a/packages/transformers/postcss/src/loadConfig.js b/packages/transformers/postcss/src/loadConfig.js index 55d2ff03cae..dd91f1cdd01 100644 --- a/packages/transformers/postcss/src/loadConfig.js +++ b/packages/transformers/postcss/src/loadConfig.js @@ -79,9 +79,10 @@ async function configHydrator( ); if (redundantPlugins.length > 0) { let filename = path.basename(resolveFrom); + let isPackageJson = filename === 'package.json'; let message; let hints = []; - if (redundantPlugins.length === pluginArray.length) { + if (!isPackageJson && redundantPlugins.length === pluginArray.length) { message = md`Parcel includes CSS transpilation and vendor prefixing by default. PostCSS config __${filename}__ contains only redundant plugins. Deleting it may significantly improve build performance.`; hints.push(md`Delete __${filename}__`); } else { @@ -96,6 +97,7 @@ async function configHydrator( let codeFrames; if (path.extname(filename) !== '.js') { let contents = await options.inputFS.readFile(resolveFrom, 'utf8'); + let prefix = isPackageJson ? '/postcss' : ''; codeFrames = [ { language: 'json', @@ -104,7 +106,7 @@ async function configHydrator( codeHighlights: generateJSONCodeHighlights( contents, redundantPlugins.map(plugin => ({ - key: `/plugins/${plugin}`, + key: `${prefix}/plugins/${plugin}`, type: 'key', })), ), From c576758fb98fcc2a12a9c49328261219826a9655 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Sun, 1 May 2022 19:08:37 +0200 Subject: [PATCH 3/3] Register swc error handler (#8032) --- .../transpilation-invalid/index.js | 9 + .../transpilation-invalid/package.json | 6 + .../transpilation-invalid/yarn.lock | 0 .../integration-tests/test/transpilation.js | 59 ++ packages/transformers/js/core/src/lib.rs | 601 +++++++++--------- 5 files changed, 379 insertions(+), 296 deletions(-) create mode 100644 packages/core/integration-tests/test/integration/transpilation-invalid/index.js create mode 100644 packages/core/integration-tests/test/integration/transpilation-invalid/package.json create mode 100644 packages/core/integration-tests/test/integration/transpilation-invalid/yarn.lock diff --git a/packages/core/integration-tests/test/integration/transpilation-invalid/index.js b/packages/core/integration-tests/test/integration/transpilation-invalid/index.js new file mode 100644 index 00000000000..0e11a344ed4 --- /dev/null +++ b/packages/core/integration-tests/test/integration/transpilation-invalid/index.js @@ -0,0 +1,9 @@ +const Boom = () => { + const littleBoom = ['hello', 'world'] + return
{...littleBoom.map(el => el)}
+} +class X { + #x(){} + #x(){} +} +console.log(Boom, X); diff --git a/packages/core/integration-tests/test/integration/transpilation-invalid/package.json b/packages/core/integration-tests/test/integration/transpilation-invalid/package.json new file mode 100644 index 00000000000..c65084718df --- /dev/null +++ b/packages/core/integration-tests/test/integration/transpilation-invalid/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "react": "^18.1.0" + }, + "browserslist": "ie 11" +} diff --git a/packages/core/integration-tests/test/integration/transpilation-invalid/yarn.lock b/packages/core/integration-tests/test/integration/transpilation-invalid/yarn.lock new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/transpilation.js b/packages/core/integration-tests/test/transpilation.js index a630267eee5..fba8e37a738 100644 --- a/packages/core/integration-tests/test/transpilation.js +++ b/packages/core/integration-tests/test/transpilation.js @@ -335,6 +335,65 @@ describe('transpilation', function () { }); }); + it('should print errors from transpilation', async function () { + let source = path.join( + __dirname, + '/integration/transpilation-invalid/index.js', + ); + // $FlowFixMe + await assert.rejects(() => bundle(source), { + name: 'BuildError', + diagnostics: [ + { + codeFrames: [ + { + codeHighlights: [ + { + message: null, + start: { + column: 15, + line: 3, + }, + end: { + column: 43, + line: 3, + }, + }, + ], + filePath: source, + }, + ], + hints: null, + message: 'Spread children are not supported in React.', + origin: '@parcel/transformer-js', + }, + { + codeFrames: [ + { + codeHighlights: [ + { + message: null, + start: { + column: 4, + line: 7, + }, + end: { + column: 4, + line: 7, + }, + }, + ], + filePath: source, + }, + ], + hints: null, + message: 'duplicate private name #x.', + origin: '@parcel/transformer-js', + }, + ], + }); + }); + describe('tests needing the real filesystem', () => { afterEach(async () => { if (process.platform === 'win32') { diff --git a/packages/transformers/js/core/src/lib.rs b/packages/transformers/js/core/src/lib.rs index 0747b4e67ce..33ca1165251 100644 --- a/packages/transformers/js/core/src/lib.rs +++ b/packages/transformers/js/core/src/lib.rs @@ -168,52 +168,7 @@ pub fn transform(config: Config) -> Result { let handler = Handler::with_emitter(true, false, Box::new(error_buffer.clone())); err.into_diagnostic(&handler).emit(); - let s = error_buffer.0.lock().unwrap().clone(); - let diagnostics: Vec = s - .iter() - .map(|diagnostic| { - let message = diagnostic.message(); - let span = diagnostic.span.clone(); - let suggestions = diagnostic.suggestions.clone(); - - let span_labels = span.span_labels(); - let code_highlights = if !span_labels.is_empty() { - let mut highlights = vec![]; - for span_label in span_labels { - highlights.push(CodeHighlight { - message: span_label.label, - loc: SourceLocation::from(&source_map, span_label.span), - }); - } - - Some(highlights) - } else { - None - }; - - let hints = if !suggestions.is_empty() { - Some( - suggestions - .into_iter() - .map(|suggestion| suggestion.msg) - .collect(), - ) - } else { - None - }; - - Diagnostic { - message, - code_highlights, - hints, - show_environment: false, - severity: DiagnosticSeverity::Error, - documentation_url: None, - } - }) - .collect(); - - result.diagnostics = Some(diagnostics); + result.diagnostics = Some(error_buffer_to_diagnostics(&error_buffer, &source_map)); Ok(result) } Ok((module, comments)) => { @@ -235,149 +190,189 @@ pub fn transform(config: Config) -> Result { SourceType::Module => true, SourceType::Script => false, }; + swc_common::GLOBALS.set(&Globals::new(), || { - helpers::HELPERS.set( - &helpers::Helpers::new( - /* external helpers from @swc/helpers */ should_import_swc_helpers, - ), - || { - let mut react_options = react::Options::default(); - if config.is_jsx { - react_options.use_spread = true; - if let Some(jsx_pragma) = &config.jsx_pragma { - react_options.pragma = jsx_pragma.clone(); - } - if let Some(jsx_pragma_frag) = &config.jsx_pragma_frag { - react_options.pragma_frag = jsx_pragma_frag.clone(); + let error_buffer = ErrorBuffer::default(); + let handler = Handler::with_emitter(true, false, Box::new(error_buffer.clone())); + swc_common::errors::HANDLER.set(&handler, || { + helpers::HELPERS.set( + &helpers::Helpers::new( + /* external helpers from @swc/helpers */ should_import_swc_helpers, + ), + || { + let mut react_options = react::Options::default(); + if config.is_jsx { + react_options.use_spread = true; + if let Some(jsx_pragma) = &config.jsx_pragma { + react_options.pragma = jsx_pragma.clone(); + } + if let Some(jsx_pragma_frag) = &config.jsx_pragma_frag { + react_options.pragma_frag = jsx_pragma_frag.clone(); + } + react_options.development = config.is_development; + react_options.refresh = if config.react_refresh { + Some(react::RefreshOptions::default()) + } else { + None + }; + + react_options.runtime = if config.automatic_jsx_runtime { + if let Some(import_source) = &config.jsx_import_source { + react_options.import_source = import_source.clone(); + } + Some(react::Runtime::Automatic) + } else { + Some(react::Runtime::Classic) + }; } - react_options.development = config.is_development; - react_options.refresh = if config.react_refresh { - Some(react::RefreshOptions::default()) - } else { - None + + let global_mark = Mark::fresh(Mark::root()); + let ignore_mark = Mark::fresh(Mark::root()); + module = { + let mut passes = chain!( + // Decorators can use type information, so must run before the TypeScript pass. + Optional::new( + decorators::decorators(decorators::Config { + legacy: true, + // Always disabled for now, SWC's implementation doesn't match TSC. + emit_metadata: false + }), + config.decorators + ), + Optional::new( + typescript::strip_with_jsx( + source_map.clone(), + typescript::Config { + pragma: Some(react_options.pragma.clone()), + pragma_frag: Some(react_options.pragma_frag.clone()), + ..Default::default() + }, + Some(&comments), + global_mark, + ), + config.is_type_script && config.is_jsx + ), + Optional::new( + typescript::strip(global_mark), + config.is_type_script && !config.is_jsx + ), + resolver_with_mark(global_mark), + Optional::new( + react::react( + source_map.clone(), + Some(&comments), + react_options, + global_mark + ), + config.is_jsx + ), + ); + + module.fold_with(&mut passes) }; - react_options.runtime = if config.automatic_jsx_runtime { - if let Some(import_source) = &config.jsx_import_source { - react_options.import_source = import_source.clone(); - } - Some(react::Runtime::Automatic) - } else { - Some(react::Runtime::Classic) + let mut decls = collect_decls(&module); + + let mut preset_env_config = swc_ecmascript::preset_env::Config { + dynamic_import: true, + ..Default::default() }; - } - - let global_mark = Mark::fresh(Mark::root()); - let ignore_mark = Mark::fresh(Mark::root()); - module = { - let mut passes = chain!( - // Decorators can use type information, so must run before the TypeScript pass. - Optional::new( - decorators::decorators(decorators::Config { - legacy: true, - // Always disabled for now, SWC's implementation doesn't match TSC. - emit_metadata: false - }), - config.decorators - ), - Optional::new( - typescript::strip_with_jsx( - source_map.clone(), - typescript::Config { - pragma: Some(react_options.pragma.clone()), - pragma_frag: Some(react_options.pragma_frag.clone()), - ..Default::default() + let versions = targets_to_versions(&config.targets); + if !config.is_swc_helpers { + // Avoid transpiling @swc/helpers so that we don't cause infinite recursion. + // Filter the versions for preset_env only so that syntax support checks + // (e.g. in esm2cjs) still work correctly. + if let Some(versions) = versions { + preset_env_config.targets = Some(Targets::Versions(versions)); + preset_env_config.shipped_proposals = true; + preset_env_config.mode = Some(Entry); + preset_env_config.bugfixes = true; + } + } + + let mut diagnostics = vec![]; + let module = { + let mut passes = chain!( + Optional::new( + TypeofReplacer { decls: &decls }, + config.source_type != SourceType::Script + ), + // Inline process.env and process.browser + Optional::new( + EnvReplacer { + replace_env: config.replace_env, + env: &config.env, + is_browser: config.is_browser, + decls: &decls, + used_env: &mut result.used_env, + source_map: &source_map, + diagnostics: &mut diagnostics }, - Some(&comments), - global_mark, + config.source_type != SourceType::Script ), - config.is_type_script && config.is_jsx - ), - Optional::new( - typescript::strip(global_mark), - config.is_type_script && !config.is_jsx - ), - resolver_with_mark(global_mark), - Optional::new( - react::react( - source_map.clone(), - Some(&comments), - react_options, - global_mark + paren_remover(Some(&comments)), + // Simplify expressions and remove dead branches so that we + // don't include dependencies inside conditionals that are always false. + expr_simplifier(Default::default()), + dead_branch_remover(), + // Inline Node fs.readFileSync calls + Optional::new( + inline_fs( + config.filename.as_str(), + source_map.clone(), + decls.clone(), + global_mark, + &config.project_root, + &mut fs_deps, + ), + should_inline_fs ), - config.is_jsx - ), - ); + ); - module.fold_with(&mut passes) - }; - - let mut decls = collect_decls(&module); - - let mut preset_env_config = swc_ecmascript::preset_env::Config { - dynamic_import: true, - ..Default::default() - }; - let versions = targets_to_versions(&config.targets); - if !config.is_swc_helpers { - // Avoid transpiling @swc/helpers so that we don't cause infinite recursion. - // Filter the versions for preset_env only so that syntax support checks - // (e.g. in esm2cjs) still work correctly. - if let Some(versions) = versions { - preset_env_config.targets = Some(Targets::Versions(versions)); - preset_env_config.shipped_proposals = true; - preset_env_config.mode = Some(Entry); - preset_env_config.bugfixes = true; - } - } - - let mut diagnostics = vec![]; - let module = { - let mut passes = chain!( - Optional::new( - TypeofReplacer { decls: &decls }, - config.source_type != SourceType::Script - ), - // Inline process.env and process.browser - Optional::new( - EnvReplacer { - replace_env: config.replace_env, - env: &config.env, - is_browser: config.is_browser, - decls: &decls, - used_env: &mut result.used_env, - source_map: &source_map, - diagnostics: &mut diagnostics - }, - config.source_type != SourceType::Script - ), - paren_remover(Some(&comments)), - // Simplify expressions and remove dead branches so that we - // don't include dependencies inside conditionals that are always false. - expr_simplifier(Default::default()), - dead_branch_remover(), - // Inline Node fs.readFileSync calls - Optional::new( - inline_fs( - config.filename.as_str(), - source_map.clone(), - decls.clone(), - global_mark, - &config.project_root, - &mut fs_deps, + module.fold_with(&mut passes) + }; + + let module = { + let mut passes = chain!( + // Insert dependencies for node globals + Optional::new( + GlobalReplacer { + source_map: &source_map, + items: &mut global_deps, + globals: HashMap::new(), + project_root: Path::new(&config.project_root), + filename: Path::new(&config.filename), + decls: &mut decls, + global_mark, + scope_hoist: config.scope_hoist + }, + config.insert_node_globals && config.source_type != SourceType::Script ), - should_inline_fs - ), - ); + // Transpile new syntax to older syntax if needed + { + let should_transpile = preset_env_config.targets.is_some(); + Optional::new( + preset_env( + global_mark, + Some(&comments), + preset_env_config, + Default::default(), + ), + should_transpile, + ) + }, + // Inject SWC helpers if needed. + helpers::inject_helpers(), + ); - module.fold_with(&mut passes) - }; + module.fold_with(&mut passes) + }; - let module = { - let mut passes = chain!( - // Insert dependencies for node globals - Optional::new( - GlobalReplacer { + let mut has_node_replacements = false; + let module = module.fold_with( + // Replace __dirname and __filename with placeholders in Node env + &mut Optional::new( + NodeReplacer { source_map: &source_map, items: &mut global_deps, globals: HashMap::new(), @@ -385,134 +380,99 @@ pub fn transform(config: Config) -> Result { filename: Path::new(&config.filename), decls: &mut decls, global_mark, - scope_hoist: config.scope_hoist + scope_hoist: config.scope_hoist, + has_node_replacements: &mut has_node_replacements, }, - config.insert_node_globals && config.source_type != SourceType::Script + config.node_replacer, + ), + ); + result.has_node_replacements = has_node_replacements; + + let module = module.fold_with( + // Collect dependencies + &mut dependency_collector( + &source_map, + &mut result.dependencies, + &decls, + ignore_mark, + &config, + &mut diagnostics, ), - // Transpile new syntax to older syntax if needed - { - let should_transpile = preset_env_config.targets.is_some(); - Optional::new( - preset_env( - global_mark, - Some(&comments), - preset_env_config, - Default::default(), - ), - should_transpile, - ) - }, - // Inject SWC helpers if needed. - helpers::inject_helpers(), ); - module.fold_with(&mut passes) - }; - - let mut has_node_replacements = false; - - let module = module.fold_with( - // Replace __dirname and __filename with placeholders in Node env - &mut Optional::new( - NodeReplacer { - source_map: &source_map, - items: &mut global_deps, - globals: HashMap::new(), - project_root: Path::new(&config.project_root), - filename: Path::new(&config.filename), - decls: &mut decls, - global_mark, - scope_hoist: config.scope_hoist, - has_node_replacements: &mut has_node_replacements, - }, - config.node_replacer, - ), - ); - - result.has_node_replacements = has_node_replacements; - - let module = module.fold_with( - // Collect dependencies - &mut dependency_collector( - &source_map, - &mut result.dependencies, - &decls, + diagnostics.extend(error_buffer_to_diagnostics(&error_buffer, &source_map)); + + if diagnostics + .iter() + .any(|d| d.severity == DiagnosticSeverity::Error) + { + result.diagnostics = Some(diagnostics); + return Ok(result); + } + + let mut collect = Collect::new( + source_map.clone(), + decls, ignore_mark, - &config, - &mut diagnostics, - ), - ); - - if diagnostics - .iter() - .any(|d| d.severity == DiagnosticSeverity::Error) - { - result.diagnostics = Some(diagnostics); - return Ok(result); - } - - let mut collect = Collect::new( - source_map.clone(), - decls, - ignore_mark, - global_mark, - config.trace_bailouts, - ); - module.visit_with(&mut collect); - if let Some(bailouts) = &collect.bailouts { - diagnostics.extend(bailouts.iter().map(|bailout| bailout.to_diagnostic())); - } - - let module = if config.scope_hoist { - let res = hoist(module, config.module_id.as_str(), &collect); - match res { - Ok((module, hoist_result, hoist_diagnostics)) => { - result.hoist_result = Some(hoist_result); - diagnostics.extend(hoist_diagnostics); - module + global_mark, + config.trace_bailouts, + ); + module.visit_with(&mut collect); + if let Some(bailouts) = &collect.bailouts { + diagnostics.extend(bailouts.iter().map(|bailout| bailout.to_diagnostic())); + } + + let module = if config.scope_hoist { + let res = hoist(module, config.module_id.as_str(), &collect); + match res { + Ok((module, hoist_result, hoist_diagnostics)) => { + result.hoist_result = Some(hoist_result); + diagnostics.extend(hoist_diagnostics); + module + } + Err(diagnostics) => { + result.diagnostics = Some(diagnostics); + return Ok(result); + } } - Err(diagnostics) => { - result.diagnostics = Some(diagnostics); - return Ok(result); + } else { + // Bail if we could not statically analyze. + if collect.static_cjs_exports && !collect.should_wrap { + result.symbol_result = Some(collect.into()); } - } - } else { - // Bail if we could not statically analyze. - if collect.static_cjs_exports && !collect.should_wrap { - result.symbol_result = Some(collect.into()); + + let (module, needs_helpers) = esm2cjs(module, versions); + result.needs_esm_helpers = needs_helpers; + module + }; + + let program = { + let mut passes = chain!(reserved_words(), hygiene(), fixer(Some(&comments)),); + module.fold_with(&mut passes) + }; + + result.dependencies.extend(global_deps); + result.dependencies.extend(fs_deps); + + if !diagnostics.is_empty() { + result.diagnostics = Some(diagnostics); } - let (module, needs_helpers) = esm2cjs(module, versions); - result.needs_esm_helpers = needs_helpers; - module - }; - - let program = { - let mut passes = chain!(reserved_words(), hygiene(), fixer(Some(&comments)),); - module.fold_with(&mut passes) - }; - - result.dependencies.extend(global_deps); - result.dependencies.extend(fs_deps); - - if !diagnostics.is_empty() { - result.diagnostics = Some(diagnostics); - } - - let (buf, mut src_map_buf) = - emit(source_map.clone(), comments, &program, config.source_maps)?; - if config.source_maps - && source_map - .build_source_map(&mut src_map_buf) - .to_writer(&mut map_buf) - .is_ok() - { - result.map = Some(String::from_utf8(map_buf).unwrap()); - } - result.code = buf; - Ok(result) - }, - ) + let (buf, mut src_map_buf) = + emit(source_map.clone(), comments, &program, config.source_maps)?; + if config.source_maps + && source_map + .build_source_map(&mut src_map_buf) + .to_writer(&mut map_buf) + .is_ok() + { + result.map = Some(String::from_utf8(map_buf).unwrap()); + } + result.code = buf; + Ok(result) + }, + ) + }) }) } } @@ -597,3 +557,52 @@ fn emit( Ok((buf, src_map_buf)) } + +fn error_buffer_to_diagnostics( + error_buffer: &ErrorBuffer, + source_map: &Lrc, +) -> Vec { + let s = error_buffer.0.lock().unwrap().clone(); + s.iter() + .map(|diagnostic| { + let message = diagnostic.message(); + let span = diagnostic.span.clone(); + let suggestions = diagnostic.suggestions.clone(); + + let span_labels = span.span_labels(); + let code_highlights = if !span_labels.is_empty() { + let mut highlights = vec![]; + for span_label in span_labels { + highlights.push(CodeHighlight { + message: span_label.label, + loc: SourceLocation::from(source_map, span_label.span), + }); + } + + Some(highlights) + } else { + None + }; + + let hints = if !suggestions.is_empty() { + Some( + suggestions + .into_iter() + .map(|suggestion| suggestion.msg) + .collect(), + ) + } else { + None + }; + + Diagnostic { + message, + code_highlights, + hints, + show_environment: false, + severity: DiagnosticSeverity::Error, + documentation_url: None, + } + }) + .collect() +}