From df7ac2a0962498fee7e0fd221a38803492b86760 Mon Sep 17 00:00:00 2001 From: shulan Date: Thu, 9 May 2024 11:33:06 +0000 Subject: [PATCH] feat: support obj external & dts support resolvedPaths (#1282) * feat: support obj external & dts support resolvedPaths * fix: test case * fix: dts file ext * fix: window path match * chore: remove print * fix: avoid break change * feat: clean code --------- Co-authored-by: brightwu <1521488775@qq.com> --- .changeset/rich-turtles-care.md | 5 + .changeset/silly-chairs-smile.md | 5 + .vscode/settings.json | 11 +- Cargo.lock | 20 ++ crates/compiler/Cargo.toml | 1 + crates/compiler/tests/common/mod.rs | 34 +- crates/compiler/tests/external.rs | 63 ++++ .../external/browser/normal/cjs/index.ts | 3 + .../external/browser/normal/cjs/output.js | 14 + .../external/browser/normal/esm/index.ts | 3 + .../external/browser/normal/esm/output.js | 14 + .../external/browser/object/cjs/index.ts | 3 + .../external/browser/object/cjs/output.js | 14 + .../external/browser/object/esm/index.ts | 3 + .../external/browser/object/esm/output.js | 14 + .../external/node/normala/cjs/index.ts | 3 + .../external/node/normala/cjs/output.js | 14 + .../external/node/normala/esm/index.ts | 3 + .../external/node/normala/esm/output.js | 14 + crates/core/src/config/custom.rs | 36 +- crates/core/src/config/external.rs | 97 ++++++ crates/core/src/config/mod.rs | 6 +- crates/macro_testing/Cargo.toml | 19 ++ crates/macro_testing/src/lib.rs | 132 ++++++++ crates/node/src/lib.rs | 2 +- crates/plugin_lazy_compilation/src/lib.rs | 16 +- crates/plugin_resolve/src/lib.rs | 32 +- crates/plugin_runtime/src/lib.rs | 39 ++- cspell.json | 3 +- examples/external/.gitignore | 22 ++ examples/external/README.md | 13 + examples/external/e2e.spec.ts | 30 ++ examples/external/farm.config.ts | 14 + examples/external/index.html | 22 ++ examples/external/package.json | 19 ++ examples/external/src/env.d.ts | 3 + examples/external/src/main.ts | 11 + examples/external/tsconfig.json | 24 ++ examples/external/tsconfig.node.json | 11 + js-plugins/dts/farm.config.mjs | 3 +- js-plugins/dts/package.json | 5 +- js-plugins/dts/src/index.ts | 17 +- js-plugins/dts/src/types.ts | 10 + packages/core/binding/binding.cjs | 195 ++++++----- packages/core/binding/index.d.ts | 50 ++- packages/core/src/config/constants.ts | 5 + packages/core/src/config/index.ts | 312 ++++++++++-------- .../normalize-config/normalize-external.ts | 90 ++++- .../normalize-config/normalize-output.ts | 3 +- packages/core/src/config/schema.ts | 7 +- packages/core/src/config/types.ts | 7 +- .../core/src/plugin/js/external-adapter.ts | 40 +++ packages/core/src/utils/json.ts | 7 + packages/core/tests/binding.spec.ts | 26 +- packages/core/tests/common.ts | 52 +-- .../__snapshots__/transform-html.spec.ts.snap | 2 +- pnpm-lock.yaml | 69 +++- 57 files changed, 1338 insertions(+), 354 deletions(-) create mode 100644 .changeset/rich-turtles-care.md create mode 100644 .changeset/silly-chairs-smile.md create mode 100644 crates/compiler/tests/external.rs create mode 100644 crates/compiler/tests/fixtures/external/browser/normal/cjs/index.ts create mode 100644 crates/compiler/tests/fixtures/external/browser/normal/cjs/output.js create mode 100644 crates/compiler/tests/fixtures/external/browser/normal/esm/index.ts create mode 100644 crates/compiler/tests/fixtures/external/browser/normal/esm/output.js create mode 100644 crates/compiler/tests/fixtures/external/browser/object/cjs/index.ts create mode 100644 crates/compiler/tests/fixtures/external/browser/object/cjs/output.js create mode 100644 crates/compiler/tests/fixtures/external/browser/object/esm/index.ts create mode 100644 crates/compiler/tests/fixtures/external/browser/object/esm/output.js create mode 100644 crates/compiler/tests/fixtures/external/node/normala/cjs/index.ts create mode 100644 crates/compiler/tests/fixtures/external/node/normala/cjs/output.js create mode 100644 crates/compiler/tests/fixtures/external/node/normala/esm/index.ts create mode 100644 crates/compiler/tests/fixtures/external/node/normala/esm/output.js create mode 100644 crates/core/src/config/external.rs create mode 100644 crates/macro_testing/Cargo.toml create mode 100644 crates/macro_testing/src/lib.rs create mode 100644 examples/external/.gitignore create mode 100644 examples/external/README.md create mode 100644 examples/external/e2e.spec.ts create mode 100644 examples/external/farm.config.ts create mode 100644 examples/external/index.html create mode 100644 examples/external/package.json create mode 100644 examples/external/src/env.d.ts create mode 100644 examples/external/src/main.ts create mode 100644 examples/external/tsconfig.json create mode 100644 examples/external/tsconfig.node.json create mode 100644 packages/core/src/plugin/js/external-adapter.ts create mode 100644 packages/core/src/utils/json.ts diff --git a/.changeset/rich-turtles-care.md b/.changeset/rich-turtles-care.md new file mode 100644 index 000000000..9422e57fd --- /dev/null +++ b/.changeset/rich-turtles-care.md @@ -0,0 +1,5 @@ +--- +'@farmfe/js-plugin-dts': patch +--- + +support custom resolvedPaths & reduce product size diff --git a/.changeset/silly-chairs-smile.md b/.changeset/silly-chairs-smile.md new file mode 100644 index 000000000..7e1ef615d --- /dev/null +++ b/.changeset/silly-chairs-smile.md @@ -0,0 +1,5 @@ +--- +'@farmfe/core': patch +--- + +support record external diff --git a/.vscode/settings.json b/.vscode/settings.json index ccc986de5..d2a36ad4d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,13 @@ { "editor.tabSize": 2, - "editor.inlineSuggest.showToolbar": "always" + "editor.inlineSuggest.showToolbar": "always", + "biome.enabled": true, + "prettier.enable": false, + "editor.defaultFormatter": "biomejs.biome", + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome", + }, + "[javascript]": { + "editor.defaultFormatter": "biomejs.biome", + }, } diff --git a/Cargo.lock b/Cargo.lock index 0e54d46e6..04b16dd35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1360,6 +1360,7 @@ dependencies = [ "farmfe_plugin_script", "farmfe_plugin_static_assets", "farmfe_plugin_tree_shake", + "farmfe_testing", "farmfe_testing_helpers", "farmfe_toolkit", "farmfe_utils 0.1.4", @@ -1622,6 +1623,19 @@ dependencies = [ "farmfe_utils 0.1.4", ] +[[package]] +name = "farmfe_testing" +version = "0.1.0" +dependencies = [ + "glob", + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.60", +] + [[package]] name = "farmfe_testing_helpers" version = "0.0.8" @@ -1970,6 +1984,12 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "glow" version = "0.12.1" diff --git a/crates/compiler/Cargo.toml b/crates/compiler/Cargo.toml index e33418a9e..235c673cf 100644 --- a/crates/compiler/Cargo.toml +++ b/crates/compiler/Cargo.toml @@ -29,6 +29,7 @@ farmfe_plugin_polyfill = { path = "../plugin_polyfill", version = "0.0.6" } farmfe_plugin_progress = { path = "../plugin_progress", version = "0.0.6" } farmfe_plugin_define = { path = "../plugin_define", version = "0.0.6" } num_cpus = "1.16.0" +farmfe_testing = { path = "../macro_testing" } [features] profile = [ diff --git a/crates/compiler/tests/common/mod.rs b/crates/compiler/tests/common/mod.rs index 80035c236..0dd205ce4 100644 --- a/crates/compiler/tests/common/mod.rs +++ b/crates/compiler/tests/common/mod.rs @@ -3,11 +3,14 @@ use std::{collections::HashMap, path::PathBuf, sync::Arc}; use farmfe_compiler::Compiler; use farmfe_core::{ config::{ - bool_or_obj::BoolOrObj, config_regex::ConfigRegex, persistent_cache::PersistentCacheConfig, - preset_env::PresetEnvConfig, Config, CssConfig, Mode, RuntimeConfig, SourcemapConfig, + bool_or_obj::BoolOrObj, + config_regex::ConfigRegex, + external::{ExternalConfig, ExternalConfigItem}, + persistent_cache::PersistentCacheConfig, + preset_env::PresetEnvConfig, + Config, CssConfig, Mode, RuntimeConfig, SourcemapConfig, }, plugin::Plugin, - resource::ResourceType, }; pub fn generate_runtime(crate_path: PathBuf) -> RuntimeConfig { @@ -35,6 +38,7 @@ pub fn generate_runtime(crate_path: PathBuf) -> RuntimeConfig { } } +#[allow(dead_code)] pub fn create_css_compiler( input: HashMap, cwd: PathBuf, @@ -79,7 +83,7 @@ pub fn create_config(cwd: PathBuf, crate_path: PathBuf) -> Config { runtime: generate_runtime(crate_path), output: farmfe_core::config::OutputConfig::default(), mode: Mode::Production, - external: vec![], + external: Default::default(), sourcemap: SourcemapConfig::Bool(false), lazy_compilation: false, progress: false, @@ -90,10 +94,24 @@ pub fn create_config(cwd: PathBuf, crate_path: PathBuf) -> Config { } } +#[allow(dead_code)] +pub fn create_compiler_with_args(cwd: PathBuf, crate_path: PathBuf, handle: F) -> Compiler +where + F: FnOnce(Config, Vec>) -> (Config, Vec>), +{ + let config = create_config(cwd, crate_path); + + let plguins = vec![]; + + let (config, plugins) = handle(config, plguins); + Compiler::new(config, plugins).expect("faile to create compiler") +} + +#[allow(dead_code)] pub fn create_with_compiler(config: Config, plugin_adapters: Vec>) -> Compiler { Compiler::new(config, plugin_adapters).expect("faile to create compiler") } - +#[allow(dead_code)] pub fn create_compiler( input: HashMap, cwd: PathBuf, @@ -128,7 +146,7 @@ pub fn create_compiler( compiler } - +#[allow(dead_code)] pub fn create_compiler_with_plugins( input: HashMap, cwd: PathBuf, @@ -180,7 +198,7 @@ pub fn create_compiler_with_plugins( compiler } - +#[allow(dead_code)] pub fn get_compiler_result(compiler: &Compiler, entry_name: Option<&String>) -> String { let resources_map = compiler.context().resources_map.lock(); let mut result = vec![]; @@ -215,10 +233,12 @@ pub fn get_compiler_result(compiler: &Compiler, entry_name: Option<&String>) -> result_file_str } +#[allow(dead_code)] pub fn load_expected_result(cwd: PathBuf) -> String { std::fs::read_to_string(cwd.join("output.js")).unwrap_or("".to_string()) } +#[allow(dead_code)] pub fn assert_compiler_result(compiler: &Compiler, entry_name: Option<&String>) { let expected_result = load_expected_result(PathBuf::from(compiler.context().config.root.clone())); let result = get_compiler_result(compiler, entry_name); diff --git a/crates/compiler/tests/external.rs b/crates/compiler/tests/external.rs new file mode 100644 index 000000000..15f13ede0 --- /dev/null +++ b/crates/compiler/tests/external.rs @@ -0,0 +1,63 @@ +mod common; +use crate::common::{assert_compiler_result, create_compiler_with_args}; +use std::{collections::HashMap, path::PathBuf}; + +use farmfe_core::config::{ + config_regex::ConfigRegex, custom::CUSTOM_CONFIG_EXTERNAL_RECORD, ModuleFormat, TargetEnv, +}; + +fn test(file: String, crate_path: String) { + let file_path_buf = PathBuf::from(file.clone()); + let create_path_buf = PathBuf::from(crate_path); + let cwd = file_path_buf.parent().unwrap(); + println!("testing test case: {:?}", cwd); + + let entry_name = "index".to_string(); + let normolized_file = file.replace('\\', "/"); + let compiler = + create_compiler_with_args(cwd.to_path_buf(), create_path_buf, |mut config, plugins| { + config.input = HashMap::from_iter(vec![(entry_name.clone(), file.clone())]); + + if normolized_file.contains("/browser/") || normolized_file.contains("/node/") { + config.output.target_env = if normolized_file.contains("browser") { + TargetEnv::Browser + } else { + TargetEnv::Node + }; + } + + if normolized_file.contains("/normal/") || normolized_file.contains("/object/") || true { + if normolized_file.contains("/object") { + config + .custom + .entry(CUSTOM_CONFIG_EXTERNAL_RECORD.to_string()) + .or_insert( + r#" +{ + "jquery": "$" +} +"# + .to_string(), + ); + } else { + config.external = vec![ConfigRegex::new("^jquery$")]; + } + } + + if normolized_file.contains("/cjs/") || normolized_file.contains("/esm/") { + config.output.format = if normolized_file.contains("cjs") { + ModuleFormat::CommonJs + } else { + ModuleFormat::EsModule + }; + } + + (config, plugins) + }); + + compiler.compile().unwrap(); + + assert_compiler_result(&compiler, Some(&entry_name)); +} + +farmfe_testing::testing! {"tests/fixtures/external/**/*.ts", test} diff --git a/crates/compiler/tests/fixtures/external/browser/normal/cjs/index.ts b/crates/compiler/tests/fixtures/external/browser/normal/cjs/index.ts new file mode 100644 index 000000000..a38a589a5 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/browser/normal/cjs/index.ts @@ -0,0 +1,3 @@ +import $ from 'jquery'; + +console.log($.find); diff --git a/crates/compiler/tests/fixtures/external/browser/normal/cjs/output.js b/crates/compiler/tests/fixtures/external/browser/normal/cjs/output.js new file mode 100644 index 000000000..441197cf1 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/browser/normal/cjs/output.js @@ -0,0 +1,14 @@ +//index.js: + (globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"0b3bded0":function (module, exports, farmRequire, farmDynamicRequire) { + console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]); +} +,},"0b3bded0");(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setExternalModules({"jquery": (globalThis||window||{})['jquery']||{}});(function(_){for(var r in _){_[r].__farm_resource_pot__='index_dcdc.js';(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"b5d64806":function (module, exports, farmRequire, farmDynamicRequire) { + "use strict"; + Object.defineProperty(exports, "__esModule", { + value: true + }); + var _interop_require_default = farmRequire("@swc/helpers/_/_interop_require_default"); + var _jquery = _interop_require_default._(farmRequire("jquery")); + console.log(_jquery.default.find); +} +,});(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setInitialLoadedResources([]);(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setDynamicModuleResourcesMap({ });var farmModuleSystem = (globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__;farmModuleSystem.bootstrap();var entry = farmModuleSystem.require("b5d64806"); \ No newline at end of file diff --git a/crates/compiler/tests/fixtures/external/browser/normal/esm/index.ts b/crates/compiler/tests/fixtures/external/browser/normal/esm/index.ts new file mode 100644 index 000000000..a38a589a5 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/browser/normal/esm/index.ts @@ -0,0 +1,3 @@ +import $ from 'jquery'; + +console.log($.find); diff --git a/crates/compiler/tests/fixtures/external/browser/normal/esm/output.js b/crates/compiler/tests/fixtures/external/browser/normal/esm/output.js new file mode 100644 index 000000000..05535e385 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/browser/normal/esm/output.js @@ -0,0 +1,14 @@ +//index.js: + (globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"0b3bded0":function (module, exports, farmRequire, farmDynamicRequire) { + console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]); +} +,},"0b3bded0");(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setExternalModules({"jquery": {...((globalThis||window||{})['jquery']||{}),__esModule:true}});(function(_){for(var r in _){_[r].__farm_resource_pot__='index_dcdc.js';(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"b5d64806":function (module, exports, farmRequire, farmDynamicRequire) { + "use strict"; + Object.defineProperty(exports, "__esModule", { + value: true + }); + var _interop_require_default = farmRequire("@swc/helpers/_/_interop_require_default"); + var _jquery = _interop_require_default._(farmRequire("jquery")); + console.log(_jquery.default.find); +} +,});(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setInitialLoadedResources([]);(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setDynamicModuleResourcesMap({ });var farmModuleSystem = (globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__;farmModuleSystem.bootstrap();var entry = farmModuleSystem.require("b5d64806"); \ No newline at end of file diff --git a/crates/compiler/tests/fixtures/external/browser/object/cjs/index.ts b/crates/compiler/tests/fixtures/external/browser/object/cjs/index.ts new file mode 100644 index 000000000..a38a589a5 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/browser/object/cjs/index.ts @@ -0,0 +1,3 @@ +import $ from 'jquery'; + +console.log($.find); diff --git a/crates/compiler/tests/fixtures/external/browser/object/cjs/output.js b/crates/compiler/tests/fixtures/external/browser/object/cjs/output.js new file mode 100644 index 000000000..304122e26 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/browser/object/cjs/output.js @@ -0,0 +1,14 @@ +//index.js: + (globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"0b3bded0":function (module, exports, farmRequire, farmDynamicRequire) { + console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]); +} +,},"0b3bded0");(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setExternalModules({"jquery": (globalThis||window||{})['$']||{}});(function(_){for(var r in _){_[r].__farm_resource_pot__='index_dcdc.js';(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"b5d64806":function (module, exports, farmRequire, farmDynamicRequire) { + "use strict"; + Object.defineProperty(exports, "__esModule", { + value: true + }); + var _interop_require_default = farmRequire("@swc/helpers/_/_interop_require_default"); + var _jquery = _interop_require_default._(farmRequire("jquery")); + console.log(_jquery.default.find); +} +,});(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setInitialLoadedResources([]);(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setDynamicModuleResourcesMap({ });var farmModuleSystem = (globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__;farmModuleSystem.bootstrap();var entry = farmModuleSystem.require("b5d64806"); \ No newline at end of file diff --git a/crates/compiler/tests/fixtures/external/browser/object/esm/index.ts b/crates/compiler/tests/fixtures/external/browser/object/esm/index.ts new file mode 100644 index 000000000..a38a589a5 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/browser/object/esm/index.ts @@ -0,0 +1,3 @@ +import $ from 'jquery'; + +console.log($.find); diff --git a/crates/compiler/tests/fixtures/external/browser/object/esm/output.js b/crates/compiler/tests/fixtures/external/browser/object/esm/output.js new file mode 100644 index 000000000..3f2cfb830 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/browser/object/esm/output.js @@ -0,0 +1,14 @@ +//index.js: + (globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'browser'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"0b3bded0":function (module, exports, farmRequire, farmDynamicRequire) { + console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]); +} +,},"0b3bded0");(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setExternalModules({"jquery": {...((globalThis||window||{})['$']||{}),__esModule:true}});(function(_){for(var r in _){_[r].__farm_resource_pot__='index_dcdc.js';(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"b5d64806":function (module, exports, farmRequire, farmDynamicRequire) { + "use strict"; + Object.defineProperty(exports, "__esModule", { + value: true + }); + var _interop_require_default = farmRequire("@swc/helpers/_/_interop_require_default"); + var _jquery = _interop_require_default._(farmRequire("jquery")); + console.log(_jquery.default.find); +} +,});(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setInitialLoadedResources([]);(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setDynamicModuleResourcesMap({ });var farmModuleSystem = (globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__;farmModuleSystem.bootstrap();var entry = farmModuleSystem.require("b5d64806"); \ No newline at end of file diff --git a/crates/compiler/tests/fixtures/external/node/normala/cjs/index.ts b/crates/compiler/tests/fixtures/external/node/normala/cjs/index.ts new file mode 100644 index 000000000..a38a589a5 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/node/normala/cjs/index.ts @@ -0,0 +1,3 @@ +import $ from 'jquery'; + +console.log($.find); diff --git a/crates/compiler/tests/fixtures/external/node/normala/cjs/output.js b/crates/compiler/tests/fixtures/external/node/normala/cjs/output.js new file mode 100644 index 000000000..1e2335359 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/node/normala/cjs/output.js @@ -0,0 +1,14 @@ +//index.js: + globalThis.nodeRequire = require;(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'node'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"0b3bded0":function (module, exports, farmRequire, farmDynamicRequire) { + console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]); +} +,},"0b3bded0");var __farm_external_module_jquery = require("jquery");(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setExternalModules({"jquery": __farm_external_module_jquery});(function(_){for(var r in _){_[r].__farm_resource_pot__='file://'+__filename;(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"b5d64806":function (module, exports, farmRequire, farmDynamicRequire) { + "use strict"; + Object.defineProperty(exports, "__esModule", { + value: true + }); + var _interop_require_default = farmRequire("@swc/helpers/_/_interop_require_default"); + var _jquery = _interop_require_default._(farmRequire("jquery")); + console.log(_jquery.default.find); +} +,});(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setInitialLoadedResources([]);(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setDynamicModuleResourcesMap({ });var farmModuleSystem = (globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__;farmModuleSystem.bootstrap();var entry = farmModuleSystem.require("b5d64806"); \ No newline at end of file diff --git a/crates/compiler/tests/fixtures/external/node/normala/esm/index.ts b/crates/compiler/tests/fixtures/external/node/normala/esm/index.ts new file mode 100644 index 000000000..a38a589a5 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/node/normala/esm/index.ts @@ -0,0 +1,3 @@ +import $ from 'jquery'; + +console.log($.find); diff --git a/crates/compiler/tests/fixtures/external/node/normala/esm/output.js b/crates/compiler/tests/fixtures/external/node/normala/esm/output.js new file mode 100644 index 000000000..0a2958072 --- /dev/null +++ b/crates/compiler/tests/fixtures/external/node/normala/esm/output.js @@ -0,0 +1,14 @@ +//index.js: + import __farmNodeModule from 'node:module';globalThis.nodeRequire = __farmNodeModule.createRequire(import.meta.url);(globalThis || window || global)['__farm_default_namespace__'] = {__FARM_TARGET_ENV__: 'node'};(function(r,e){var t={};function n(r){return Promise.resolve(o(r))}function o(e){if(t[e])return t[e].exports;var i={id:e,exports:{}};t[e]=i;r[e](i,i.exports,o,n);return i.exports}o(e)})({"0b3bded0":function (module, exports, farmRequire, farmDynamicRequire) { + console.log("runtime/index.js")(globalThis || window || global)["__farm_default_namespace__"].__farm_module_system__.setPlugins([]); +} +,},"0b3bded0");import * as __farm_external_module_jquery from "jquery";(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setExternalModules({"jquery": {...__farm_external_module_jquery,__esModule:true}});(function(_){for(var r in _){_[r].__farm_resource_pot__='index_dcdc.js';(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.register(r,_[r])}})({"b5d64806":function (module, exports, farmRequire, farmDynamicRequire) { + "use strict"; + Object.defineProperty(exports, "__esModule", { + value: true + }); + var _interop_require_default = farmRequire("@swc/helpers/_/_interop_require_default"); + var _jquery = _interop_require_default._(farmRequire("jquery")); + console.log(_jquery.default.find); +} +,});(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setInitialLoadedResources([]);(globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__.setDynamicModuleResourcesMap({ });var farmModuleSystem = (globalThis || window || global)['__farm_default_namespace__'].__farm_module_system__;farmModuleSystem.bootstrap();var entry = farmModuleSystem.require("b5d64806"); \ No newline at end of file diff --git a/crates/core/src/config/custom.rs b/crates/core/src/config/custom.rs index 7a5c562a0..4a20683dc 100644 --- a/crates/core/src/config/custom.rs +++ b/crates/core/src/config/custom.rs @@ -1,8 +1,15 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use crate::context::CompilationContext; +use super::{ + config_regex::ConfigRegex, + external::{ExternalConfig, ExternalObject}, + Config, +}; + const CUSTOM_CONFIG_RUNTIME_ISOLATE: &str = "runtime.isolate"; +pub const CUSTOM_CONFIG_EXTERNAL_RECORD: &str = "external.record"; pub fn get_config_runtime_isolate(context: &Arc) -> bool { if let Some(val) = context.config.custom.get(CUSTOM_CONFIG_RUNTIME_ISOLATE) { @@ -11,3 +18,30 @@ pub fn get_config_runtime_isolate(context: &Arc) -> bool { false } } + +pub fn get_config_external_record(config: &Config) -> ExternalConfig { + if let Some(val) = config.custom.get(CUSTOM_CONFIG_EXTERNAL_RECORD) { + if val.is_empty() { + return ExternalConfig::new(); + } + + let external: HashMap = serde_json::from_str(val) + .unwrap_or_else(|_| panic!("failed parse record external {:?}", val)); + + let mut external_config = ExternalConfig::new(); + + for (regex, name) in external { + external_config + .0 + .push(super::external::ExternalConfigItem::Object( + ExternalObject { + pattern: ConfigRegex::new(®ex), + global_name: name, + }, + )); + } + external_config + } else { + ExternalConfig::new() + } +} diff --git a/crates/core/src/config/external.rs b/crates/core/src/config/external.rs new file mode 100644 index 000000000..6996653e7 --- /dev/null +++ b/crates/core/src/config/external.rs @@ -0,0 +1,97 @@ +use serde::{Deserialize, Serialize}; + +use super::{config_regex::ConfigRegex, custom::get_config_external_record, Config}; + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase", default)] +pub struct ExternalObject { + pub pattern: ConfigRegex, + pub global_name: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum ExternalConfigItem { + Default(ConfigRegex), + Object(ExternalObject), +} + +impl Default for ExternalConfigItem { + fn default() -> Self { + Self::Default(ConfigRegex::default()) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +pub struct ExternalConfig(pub Vec); + +impl ExternalConfig { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn find_match(&self, source: &str) -> Option<&ExternalConfigItem> { + self.0.iter().find(|item| item.is_match(source)) + } + + pub fn is_external(&self, source: &str) -> bool { + self.find_match(source).is_some() + } +} + +impl ExternalConfigItem { + pub fn is_match(&self, source: &str) -> bool { + match self { + Self::Default(regex) => regex.is_match(source), + Self::Object(kv) => kv.pattern.is_match(source), + } + } + + pub fn source(&self, source: &str) -> String { + match self { + Self::Default(_) => source.to_string(), + Self::Object(obj) => obj.global_name.to_string(), + } + } +} + +impl From<&Config> for ExternalConfig { + fn from(config: &Config) -> Self { + let mut external_config = get_config_external_record(config); + + for external in &config.external { + external_config + .0 + .push(ExternalConfigItem::Default(external.clone())) + } + + external_config + } +} + +#[cfg(test)] +mod tests { + use serde::{Deserialize, Serialize}; + use serde_json::json; + + use super::ExternalConfig; + + #[test] + fn test() { + #[derive(Debug, Deserialize, Serialize)] + struct D { + external: ExternalConfig, + } + + let value: D = serde_json::from_str( + json!({ + "external": ["^node:.+$", { "pattern": "jquery", "globalName": "$" }] + }) + .to_string() + .as_str(), + ) + .unwrap(); + + println!("{:#?}", value); + } +} diff --git a/crates/core/src/config/mod.rs b/crates/core/src/config/mod.rs index 469bf924e..789c9996b 100644 --- a/crates/core/src/config/mod.rs +++ b/crates/core/src/config/mod.rs @@ -6,8 +6,7 @@ use swc_css_prefixer::options::Targets; use swc_ecma_parser::{EsConfig, TsConfig}; use self::{ - bool_or_obj::BoolOrObj, comments::CommentsConfig, config_regex::ConfigRegex, html::HtmlConfig, - partial_bundling::PartialBundlingConfig, preset_env::PresetEnvConfig, script::ScriptConfig, + bool_or_obj::BoolOrObj, comments::CommentsConfig, config_regex::ConfigRegex, external::ExternalConfig, html::HtmlConfig, partial_bundling::PartialBundlingConfig, preset_env::PresetEnvConfig, script::ScriptConfig }; pub const FARM_MODULE_SYSTEM: &str = "__farm_module_system__"; @@ -28,6 +27,7 @@ pub mod partial_bundling; pub mod persistent_cache; pub mod preset_env; pub mod script; +pub mod external; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase", default)] @@ -74,7 +74,7 @@ impl Default for Config { mode: Mode::Development, resolve: ResolveConfig::default(), define: HashMap::new(), - external: vec![], + external: Default::default(), runtime: Default::default(), script: Default::default(), css: Default::default(), diff --git a/crates/macro_testing/Cargo.toml b/crates/macro_testing/Cargo.toml new file mode 100644 index 000000000..ada49531e --- /dev/null +++ b/crates/macro_testing/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "farmfe_testing" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] + +proc-macro-error = "1.0.4" +proc-macro2 = "1.0.81" +quote = "1.0.36" +syn = "2.0.60" +glob = "0.3.1" +regex = "1.7.3" +heck = "0.4.1" \ No newline at end of file diff --git a/crates/macro_testing/src/lib.rs b/crates/macro_testing/src/lib.rs new file mode 100644 index 000000000..e9bc4bd5b --- /dev/null +++ b/crates/macro_testing/src/lib.rs @@ -0,0 +1,132 @@ +use std::path::PathBuf; + +use glob; +use heck::AsSnakeCase; +use proc_macro::TokenStream; +use proc_macro_error::abort; +use quote::quote; +use syn; + +struct Testing { + pattern: syn::ExprLit, + handler: syn::Path, +} + +#[derive(Debug)] +struct WalkFiles { + file: PathBuf, + cwd: PathBuf, + base_dir: PathBuf, +} + +fn safe_test_name(file: &PathBuf) -> String { + use regex::Regex; + + let replace_valid_syntax = Regex::new("[^a-zA-Z0-9_]+").unwrap(); + let replace_start_syntax = Regex::new("^[^a-zA-Z]").unwrap(); + + replace_start_syntax + .replace_all( + &replace_valid_syntax.replace_all(&file.to_string_lossy().to_string(), "_"), + "_", + ) + .to_string() +} + +impl syn::parse::Parse for Testing { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let path: syn::ExprLit = input.parse()?; + let _: syn::Token!(,) = input.parse()?; + let handler: syn::Path = input.parse()?; + + Ok(Testing { + pattern: path, + handler, + }) + } +} + +impl Testing { + fn files(&self) -> Result, &str> { + // let files = vec![]; + let pattern = match &self.pattern.lit { + syn::Lit::Str(str) => str.value().to_string(), + _ => abort!(self.pattern, "expected string literal"), + }; + + let mut files: Vec = vec![]; + + let base_dir = PathBuf::from( + std::env::var("CARGO_MANIFEST_DIR").map_err(|_| "failed to get CARGO_MANIFEST_DIR")?, + ); + + let pattern = base_dir.join(&pattern); + + for file in + glob::glob(&pattern.to_string_lossy().to_string()).map_err(|_| "failed match files")? + { + match file { + Ok(path) => { + files.push(WalkFiles { + file: path.clone(), + cwd: path.parent().unwrap().to_path_buf(), + base_dir: base_dir.clone(), + }); + } + Err(e) => { + abort!(e.to_string(), "{:?}", e.to_string()); + } + } + } + + Ok(files) + } +} + +impl Testing { + fn to_tokens(&self) -> Result { + let files = self.files()?; + + let mut output = proc_macro2::TokenStream::new(); + + let f = &self.handler; + + for WalkFiles { file, base_dir, .. } in files { + let relative = file.strip_prefix(&base_dir).unwrap(); + let test_name = syn::Ident::new( + &AsSnakeCase(safe_test_name(&relative.into())).to_string(), + self.pattern.lit.span(), + ); + let file = file.to_string_lossy().to_string(); + let base_dir = base_dir.to_string_lossy().to_string(); + + output.extend(quote! { + #[test] + pub fn #test_name() { + let test_file = #file; + let base_dir = #base_dir; + + #f(test_file.to_string(), base_dir.to_string()); + } + }); + } + + Ok(output.into()) + } +} + +impl Into for Testing { + fn into(self) -> TokenStream { + match self.to_tokens() { + Ok(tokens) => tokens.into(), + Err(err) => abort!(err, "{}", err), + } + } +} + +#[proc_macro] +pub fn testing(input: TokenStream) -> TokenStream { + let testing = syn::parse_macro_input!(input as Testing); + + testing.into() +} diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs index 5544fc786..f8876e625 100644 --- a/crates/node/src/lib.rs +++ b/crates/node/src/lib.rs @@ -290,7 +290,7 @@ impl JsCompiler { thread_safe_callback.call((), ThreadsafeFunctionCallMode::Blocking); }, sync, - generate_update_resource + generate_update_resource, ) .map_err(|e| napi::Error::new(Status::GenericFailure, format!("{}", e))) { diff --git a/crates/plugin_lazy_compilation/src/lib.rs b/crates/plugin_lazy_compilation/src/lib.rs index fbde3e283..001cdc29d 100644 --- a/crates/plugin_lazy_compilation/src/lib.rs +++ b/crates/plugin_lazy_compilation/src/lib.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use farmfe_core::{ - config::{Config, FARM_MODULE_SYSTEM}, + config::{external::ExternalConfig, Config, FARM_MODULE_SYSTEM}, module::{ModuleId, ModuleType}, plugin::{Plugin, PluginHookContext, PluginLoadHookResult, PluginResolveHookParam, ResolveKind}, }; @@ -106,14 +106,14 @@ impl Plugin for FarmPluginLazyCompilation { } } + let is_external = || { + let external_config = ExternalConfig::from(&*context.config); + + external_config.is_external(¶m.source) + }; + // if the source is imported by dynamic import and it's not external source - if matches!(param.kind, ResolveKind::DynamicImport) - && !context - .config - .external - .iter() - .any(|e| e.is_match(¶m.source)) - { + if matches!(param.kind, ResolveKind::DynamicImport) && !is_external() { let resolve_result = context.plugin_driver.resolve( param, context, diff --git a/crates/plugin_resolve/src/lib.rs b/crates/plugin_resolve/src/lib.rs index c24063844..6ba8c3d3f 100644 --- a/crates/plugin_resolve/src/lib.rs +++ b/crates/plugin_resolve/src/lib.rs @@ -1,9 +1,13 @@ -use std::{collections::HashMap, path::Path, sync::Arc}; +use std::{ + collections::HashMap, + path::Path, + sync::{Arc, RwLock}, +}; use farmfe_core::{ - config::Config, + config::{external::ExternalConfig, Config}, context::CompilationContext, - error::Result, + error::{CompilationError, Result}, farm_profile_function, farm_profile_scope, plugin::{ Plugin, PluginHookContext, PluginResolveHookParam, PluginResolveHookResult, ResolveKind, @@ -18,6 +22,7 @@ pub mod resolver; pub struct FarmPluginResolve { root: String, resolver: Resolver, + external_config: RwLock>, } impl FarmPluginResolve { @@ -25,6 +30,7 @@ impl FarmPluginResolve { Self { root: config.root.clone(), resolver: Resolver::new(), + external_config: RwLock::new(None) } } } @@ -42,6 +48,24 @@ impl Plugin for FarmPluginResolve { ) -> Result> { farm_profile_function!("plugin_resolve::resolve".to_string()); + let mut external_config = self + .external_config + .read() + .map_err(|_| CompilationError::GenericError("failed get lock".to_string()))?; + + if external_config.is_none() { + drop(external_config); + let mut external_config_mut = self.external_config.write().unwrap(); + + *external_config_mut = Some(ExternalConfig::from(&*context.config)); + + drop(external_config_mut); + + external_config = self.external_config.read().unwrap(); + } + + let external_config = external_config.as_ref().unwrap(); + let source = ¶m.source; let query = parse_query(source); @@ -71,7 +95,7 @@ impl Plugin for FarmPluginResolve { { farm_profile_scope!("plugin_resolve::resolve::check_external".to_string()); // check external first, if the source is set as external, return it immediately - if context.config.external.iter().any(|e| e.is_match(source)) { + if external_config.is_external(source) { return Ok(Some(PluginResolveHookResult { resolved_path: param.source.clone(), external: true, diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index a5c8c014e..e02250485 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -8,8 +8,9 @@ use std::{ use farmfe_core::{ config::{ - config_regex::ConfigRegex, partial_bundling::PartialBundlingEnforceResourceConfig, Config, - ModuleFormat, TargetEnv, FARM_MODULE_SYSTEM, + config_regex::ConfigRegex, external::ExternalConfig, + partial_bundling::PartialBundlingEnforceResourceConfig, Config, ModuleFormat, TargetEnv, + FARM_MODULE_SYSTEM, }, context::CompilationContext, enhanced_magic_string::types::SourceMapOptions, @@ -389,6 +390,7 @@ impl Plugin for FarmPluginRuntime { let async_modules = self.get_async_modules(context); let async_modules = async_modules.downcast_ref::>().unwrap(); let module_graph = context.module_graph.read(); + let external_config = ExternalConfig::from(&*context.config); let RenderedJsResourcePot { mut bundle, rendered_modules, @@ -441,21 +443,28 @@ impl Plugin for FarmPluginRuntime { } else if !external_modules.is_empty() && context.config.output.target_env == TargetEnv::Browser { + let mut external_objs = Vec::new(); + + for source in external_modules { + let replace_source = match external_config.find_match(&source) { + Some(v) => Ok(v.source(&source)), + None => Err(CompilationError::GenericError(format!( + "cannot find external source: {:?}", + source + ))), + }?; + + let source_obj = format!("(globalThis||window||{{}})['{}']||{{}}", replace_source); + external_objs.push(if context.config.output.format == ModuleFormat::EsModule { + format!("{source:?}: {{...({source_obj}),__esModule:true}}") + } else { + format!("{source:?}: {source_obj}") + }); + } + let prepend_str = format!( "{farm_global_this}.{FARM_MODULE_SYSTEM}.setExternalModules({{{}}});", - external_modules - .into_iter() - .map(|source| { - // TODO: make window['{source}'] configurable. - let source_obj = format!("(globalThis||window||{{}})['{}']||{{}}", source); - if context.config.output.format == ModuleFormat::EsModule { - format!("{source:?}: {{...({source_obj}),__esModule:true}}") - } else { - format!("{source:?}: {source_obj}") - } - }) - .collect::>() - .join(",") + external_objs.join(",") ); external_modules_str = Some(prepend_str); } diff --git a/cspell.json b/cspell.json index 79f6657c1..4431cee1d 100644 --- a/cspell.json +++ b/cspell.json @@ -161,7 +161,8 @@ "wasix", "wechat", "xlink", - "Yuxi" + "Yuxi", + "jquery" ], "ignorePaths": [ "pnpm-lock.yaml", diff --git a/examples/external/.gitignore b/examples/external/.gitignore new file mode 100644 index 000000000..a4cf82d2c --- /dev/null +++ b/examples/external/.gitignore @@ -0,0 +1,22 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.sln +*.sw? diff --git a/examples/external/README.md b/examples/external/README.md new file mode 100644 index 000000000..535eafb03 --- /dev/null +++ b/examples/external/README.md @@ -0,0 +1,13 @@ +# Farm External + +## string + +```ts +['react-dom']; +``` + +## record + +```ts +[{ 'react-dom': 'ReactDom' }]; +``` diff --git a/examples/external/e2e.spec.ts b/examples/external/e2e.spec.ts new file mode 100644 index 000000000..768299591 --- /dev/null +++ b/examples/external/e2e.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from 'vitest'; +import { startProjectAndTest } from '../../e2e/vitestSetup'; +import path, { basename, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { readFileSync, writeFileSync } from 'fs'; + +const name = basename(import.meta.url); +const projectPath = dirname(fileURLToPath(import.meta.url)); + +test(`e2e tests - ${name}`, async () => { + const runTest = (command?: 'start' | 'preview') => + startProjectAndTest( + projectPath, + async (page) => { + console.log(page.url()); + await page.waitForSelector('div#root', { + timeout: 10000 + }); + const root = await page.$('#root'); + const innerHTML = await root?.innerHTML(); + expect(innerHTML).toContain('
jquery: jquery
'); + expect(innerHTML).toContain('
react-dom: react-dom
'); + expect(innerHTML).toContain('
react: react + + + + + + Farm + + + +
+ + + diff --git a/examples/external/package.json b/examples/external/package.json new file mode 100644 index 000000000..30c6bf246 --- /dev/null +++ b/examples/external/package.json @@ -0,0 +1,19 @@ +{ + "name": "@farmfe-examples/external", + "version": "0.0.1", + "type": "module", + "private": true, + "scripts": { + "dev": "farm", + "start": "farm", + "build": "farm build", + "preview": "farm preview", + "clean": "farm clean" + }, + "devDependencies": { + "@farmfe/cli": "^1.0.1", + "@farmfe/core": "^1.1.1", + "@types/jquery": "^3.5.29", + "typescript": "^5.4.3" + } +} diff --git a/examples/external/src/env.d.ts b/examples/external/src/env.d.ts new file mode 100644 index 000000000..3008a6ff1 --- /dev/null +++ b/examples/external/src/env.d.ts @@ -0,0 +1,3 @@ +declare module 'react'; +declare module 'react-dom'; +declare module 'jquery'; diff --git a/examples/external/src/main.ts b/examples/external/src/main.ts new file mode 100644 index 000000000..8b21e7de6 --- /dev/null +++ b/examples/external/src/main.ts @@ -0,0 +1,11 @@ +import ReactDom from 'react-dom'; +import React from 'react'; +import $ from 'jquery'; + +document.body.innerHTML = ` +
+
jquery: ${$}
+
react-dom: ${ReactDom}
+
react: ${React}
+
+`; diff --git a/examples/external/tsconfig.json b/examples/external/tsconfig.json new file mode 100644 index 000000000..145dd74ad --- /dev/null +++ b/examples/external/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/examples/external/tsconfig.node.json b/examples/external/tsconfig.node.json new file mode 100644 index 000000000..8d4232518 --- /dev/null +++ b/examples/external/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["farm.config.ts"] +} diff --git a/js-plugins/dts/farm.config.mjs b/js-plugins/dts/farm.config.mjs index a1eb23cb1..b2c64c5f8 100644 --- a/js-plugins/dts/farm.config.mjs +++ b/js-plugins/dts/farm.config.mjs @@ -1,5 +1,3 @@ -import { builtinModules } from 'module'; - /** * @type {import('@farmfe/core').UserConfig} */ @@ -14,6 +12,7 @@ export default { targetEnv: 'node', format: 'cjs' }, + external: ['typescript', 'fast-glob', 'ts-morph', 'fs-extra'], partialBundling: { enforceResources: [ { diff --git a/js-plugins/dts/package.json b/js-plugins/dts/package.json index 3ef4fd1b2..46b039aa9 100644 --- a/js-plugins/dts/package.json +++ b/js-plugins/dts/package.json @@ -25,10 +25,11 @@ "author": "", "license": "ISC", "dependencies": { - "fast-glob": "^3.2.12", "chalk": "^5.2.0", + "fast-glob": "^3.2.12", "fs-extra": "^11.1.1", - "ts-morph": "^19.0.0" + "ts-morph": "^19.0.0", + "typescript": "^5.4.5" }, "devDependencies": { "@farmfe/cli": "workspace:*", diff --git a/js-plugins/dts/src/index.ts b/js-plugins/dts/src/index.ts index 407359f78..52d6daa54 100644 --- a/js-plugins/dts/src/index.ts +++ b/js-plugins/dts/src/index.ts @@ -1,4 +1,5 @@ import type { JsPlugin } from '@farmfe/core'; +import path from 'node:path'; import Context from './context.js'; import { pluginName } from './options.js'; @@ -6,9 +7,11 @@ import { tryToReadFileSync } from './utils.js'; import type { DtsPluginOptions } from './types.js'; +const extension = ['.ts', '.tsx'].map((ext) => `${ext}$`); + export default function farmDtsPlugin(options?: DtsPluginOptions): JsPlugin { const ctx = new Context(); - // TODO support vue other framework file type + return { name: pluginName, priority: 1000, @@ -17,7 +20,11 @@ export default function farmDtsPlugin(options?: DtsPluginOptions): JsPlugin { }, load: { filters: { - resolvedPaths: ['.ts$'] + resolvedPaths: [ + ...(Array.isArray(options?.resolvedPaths) + ? options.resolvedPaths + : extension) + ] }, async executor(params) { const { resolvedPath } = params; @@ -34,10 +41,14 @@ export default function farmDtsPlugin(options?: DtsPluginOptions): JsPlugin { }, async executor(params) { const { resolvedPath, content } = params; + const [url] = resolvedPath.split('?'); ctx.handleTransform(resolvedPath); + + const ext = path.extname(url).slice(1); + return { content, - moduleType: 'ts' + moduleType: ext || 'ts' }; } }, diff --git a/js-plugins/dts/src/types.ts b/js-plugins/dts/src/types.ts index 535849ec0..4f3816586 100644 --- a/js-plugins/dts/src/types.ts +++ b/js-plugins/dts/src/types.ts @@ -1,6 +1,16 @@ import type { Diagnostic, ts } from 'ts-morph'; export interface DtsPluginOptions { + /** + * match files + * + * @default + * ```ts + * [".ts$", ".tsx$"] + * ``` + **/ + resolvedPaths?: string[]; + /** * Depends on the root directory */ diff --git a/packages/core/binding/binding.cjs b/packages/core/binding/binding.cjs index a24eba719..681f9acdb 100644 --- a/packages/core/binding/binding.cjs +++ b/packages/core/binding/binding.cjs @@ -4,30 +4,27 @@ /* auto-generated by NAPI-RS */ -const { existsSync, readFileSync } = require('fs'); -const { join } = require('path'); +const { existsSync, readFileSync } = require('fs') +const { join } = require('path') -const { platform, arch } = process; +const { platform, arch } = process -let nativeBinding = null; -let localFileExisted = false; -let loadError = null; +let nativeBinding = null +let localFileExisted = false +let loadError = null function isMusl() { // For Node 10 if (!process.report || typeof process.report.getReport !== 'function') { try { - const lddPath = require('child_process') - .execSync('which ldd') - .toString() - .trim(); - return readFileSync(lddPath, 'utf8').includes('musl'); + const lddPath = require('child_process').execSync('which ldd').toString().trim() + return readFileSync(lddPath, 'utf8').includes('musl') } catch (e) { - return true; + return true } } else { - const { glibcVersionRuntime } = process.report.getReport().header; - return !glibcVersionRuntime; + const { glibcVersionRuntime } = process.report.getReport().header + return !glibcVersionRuntime } } @@ -35,233 +32,227 @@ switch (platform) { case 'android': switch (arch) { case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'farm.android-arm64.node') - ); + localFileExisted = existsSync(join(__dirname, 'farm.android-arm64.node')) try { if (localFileExisted) { - nativeBinding = require('./farm.android-arm64.node'); + nativeBinding = require('./farm.android-arm64.node') } else { - nativeBinding = require('@farmfe/core-android-arm64'); + nativeBinding = require('@farmfe/core-android-arm64') } } catch (e) { - loadError = e; + loadError = e } - break; + break case 'arm': - localFileExisted = existsSync( - join(__dirname, 'farm.android-arm-eabi.node') - ); + localFileExisted = existsSync(join(__dirname, 'farm.android-arm-eabi.node')) try { if (localFileExisted) { - nativeBinding = require('./farm.android-arm-eabi.node'); + nativeBinding = require('./farm.android-arm-eabi.node') } else { - nativeBinding = require('@farmfe/core-android-arm-eabi'); + nativeBinding = require('@farmfe/core-android-arm-eabi') } } catch (e) { - loadError = e; + loadError = e } - break; + break default: - throw new Error(`Unsupported architecture on Android ${arch}`); + throw new Error(`Unsupported architecture on Android ${arch}`) } - break; + break case 'win32': switch (arch) { case 'x64': localFileExisted = existsSync( join(__dirname, 'farm.win32-x64-msvc.node') - ); + ) try { if (localFileExisted) { - nativeBinding = require('./farm.win32-x64-msvc.node'); + nativeBinding = require('./farm.win32-x64-msvc.node') } else { - nativeBinding = require('@farmfe/core-win32-x64-msvc'); + nativeBinding = require('@farmfe/core-win32-x64-msvc') } } catch (e) { - loadError = e; + loadError = e } - break; + break case 'ia32': localFileExisted = existsSync( join(__dirname, 'farm.win32-ia32-msvc.node') - ); + ) try { if (localFileExisted) { - nativeBinding = require('./farm.win32-ia32-msvc.node'); + nativeBinding = require('./farm.win32-ia32-msvc.node') } else { - nativeBinding = require('@farmfe/core-win32-ia32-msvc'); + nativeBinding = require('@farmfe/core-win32-ia32-msvc') } } catch (e) { - loadError = e; + loadError = e } - break; + break case 'arm64': localFileExisted = existsSync( join(__dirname, 'farm.win32-arm64-msvc.node') - ); + ) try { if (localFileExisted) { - nativeBinding = require('./farm.win32-arm64-msvc.node'); + nativeBinding = require('./farm.win32-arm64-msvc.node') } else { - nativeBinding = require('@farmfe/core-win32-arm64-msvc'); + nativeBinding = require('@farmfe/core-win32-arm64-msvc') } } catch (e) { - loadError = e; + loadError = e } - break; + break default: - throw new Error(`Unsupported architecture on Windows: ${arch}`); + throw new Error(`Unsupported architecture on Windows: ${arch}`) } - break; + break case 'darwin': - localFileExisted = existsSync( - join(__dirname, 'farm.darwin-universal.node') - ); + localFileExisted = existsSync(join(__dirname, 'farm.darwin-universal.node')) try { if (localFileExisted) { - nativeBinding = require('./farm.darwin-universal.node'); + nativeBinding = require('./farm.darwin-universal.node') } else { - nativeBinding = require('@farmfe/core-darwin-universal'); + nativeBinding = require('@farmfe/core-darwin-universal') } - break; + break } catch {} switch (arch) { case 'x64': - localFileExisted = existsSync(join(__dirname, 'farm.darwin-x64.node')); + localFileExisted = existsSync(join(__dirname, 'farm.darwin-x64.node')) try { if (localFileExisted) { - nativeBinding = require('./farm.darwin-x64.node'); + nativeBinding = require('./farm.darwin-x64.node') } else { - nativeBinding = require('@farmfe/core-darwin-x64'); + nativeBinding = require('@farmfe/core-darwin-x64') } } catch (e) { - loadError = e; + loadError = e } - break; + break case 'arm64': localFileExisted = existsSync( join(__dirname, 'farm.darwin-arm64.node') - ); + ) try { if (localFileExisted) { - nativeBinding = require('./farm.darwin-arm64.node'); + nativeBinding = require('./farm.darwin-arm64.node') } else { - nativeBinding = require('@farmfe/core-darwin-arm64'); + nativeBinding = require('@farmfe/core-darwin-arm64') } } catch (e) { - loadError = e; + loadError = e } - break; + break default: - throw new Error(`Unsupported architecture on macOS: ${arch}`); + throw new Error(`Unsupported architecture on macOS: ${arch}`) } - break; + break case 'freebsd': if (arch !== 'x64') { - throw new Error(`Unsupported architecture on FreeBSD: ${arch}`); + throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) } - localFileExisted = existsSync(join(__dirname, 'farm.freebsd-x64.node')); + localFileExisted = existsSync(join(__dirname, 'farm.freebsd-x64.node')) try { if (localFileExisted) { - nativeBinding = require('./farm.freebsd-x64.node'); + nativeBinding = require('./farm.freebsd-x64.node') } else { - nativeBinding = require('@farmfe/core-freebsd-x64'); + nativeBinding = require('@farmfe/core-freebsd-x64') } } catch (e) { - loadError = e; + loadError = e } - break; + break case 'linux': switch (arch) { case 'x64': if (isMusl()) { localFileExisted = existsSync( join(__dirname, 'farm.linux-x64-musl.node') - ); + ) try { if (localFileExisted) { - nativeBinding = require('./farm.linux-x64-musl.node'); + nativeBinding = require('./farm.linux-x64-musl.node') } else { - nativeBinding = require('@farmfe/core-linux-x64-musl'); + nativeBinding = require('@farmfe/core-linux-x64-musl') } } catch (e) { - loadError = e; + loadError = e } } else { localFileExisted = existsSync( join(__dirname, 'farm.linux-x64-gnu.node') - ); + ) try { if (localFileExisted) { - nativeBinding = require('./farm.linux-x64-gnu.node'); + nativeBinding = require('./farm.linux-x64-gnu.node') } else { - nativeBinding = require('@farmfe/core-linux-x64-gnu'); + nativeBinding = require('@farmfe/core-linux-x64-gnu') } } catch (e) { - loadError = e; + loadError = e } } - break; + break case 'arm64': if (isMusl()) { localFileExisted = existsSync( join(__dirname, 'farm.linux-arm64-musl.node') - ); + ) try { if (localFileExisted) { - nativeBinding = require('./farm.linux-arm64-musl.node'); + nativeBinding = require('./farm.linux-arm64-musl.node') } else { - nativeBinding = require('@farmfe/core-linux-arm64-musl'); + nativeBinding = require('@farmfe/core-linux-arm64-musl') } } catch (e) { - loadError = e; + loadError = e } } else { localFileExisted = existsSync( join(__dirname, 'farm.linux-arm64-gnu.node') - ); + ) try { if (localFileExisted) { - nativeBinding = require('./farm.linux-arm64-gnu.node'); + nativeBinding = require('./farm.linux-arm64-gnu.node') } else { - nativeBinding = require('@farmfe/core-linux-arm64-gnu'); + nativeBinding = require('@farmfe/core-linux-arm64-gnu') } } catch (e) { - loadError = e; + loadError = e } } - break; + break case 'arm': localFileExisted = existsSync( join(__dirname, 'farm.linux-arm-gnueabihf.node') - ); + ) try { if (localFileExisted) { - nativeBinding = require('./farm.linux-arm-gnueabihf.node'); + nativeBinding = require('./farm.linux-arm-gnueabihf.node') } else { - nativeBinding = require('@farmfe/core-linux-arm-gnueabihf'); + nativeBinding = require('@farmfe/core-linux-arm-gnueabihf') } } catch (e) { - loadError = e; + loadError = e } - break; + break default: - throw new Error(`Unsupported architecture on Linux: ${arch}`); + throw new Error(`Unsupported architecture on Linux: ${arch}`) } - break; + break default: - throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`); + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) } if (!nativeBinding) { if (loadError) { - throw loadError; + throw loadError } - throw new Error(`Failed to load native binding`); + throw new Error(`Failed to load native binding`) } -const { JsPluginTransformHtmlHookOrder, Compiler } = nativeBinding; +const { JsPluginTransformHtmlHookOrder, Compiler } = nativeBinding -module.exports.JsPluginTransformHtmlHookOrder = JsPluginTransformHtmlHookOrder; -module.exports.Compiler = Compiler; +module.exports.JsPluginTransformHtmlHookOrder = JsPluginTransformHtmlHookOrder +module.exports.Compiler = Compiler diff --git a/packages/core/binding/index.d.ts b/packages/core/binding/index.d.ts index 17a8662c4..1c8da6404 100644 --- a/packages/core/binding/index.d.ts +++ b/packages/core/binding/index.d.ts @@ -23,7 +23,12 @@ export type ResolveKind = export * from './binding.js'; import { Compiler } from './binding.js'; import type { WatchOptions } from 'chokidar'; -import { JsMinifyOptions, SwcPresetEnvOptions, ScriptDecoratorsConfig, ScriptParseConfig } from './swc-config.js'; +import { + JsMinifyOptions, + SwcPresetEnvOptions, + ScriptDecoratorsConfig, + ScriptParseConfig +} from './swc-config.js'; export default Compiler; export const bindingPath: string; @@ -136,7 +141,16 @@ export interface OutputConfig { * You can also set target env version like `node16`, `node-legacy`, 'browser-legacy`, 'browser-es2015', 'browser-2017', 'browser-esnext'. Farm will automatically downgrade syntax and inject polyfill according to the specified target env. * @default 'browser' */ - targetEnv?: 'browser' | 'node' | 'node16' | 'node-legacy' | 'node-next' | 'browser-legacy' | 'browser-es2015' | 'browser-es2017' | 'browser-esnext'; + targetEnv?: + | 'browser' + | 'node' + | 'node16' + | 'node-legacy' + | 'node-next' + | 'browser-legacy' + | 'browser-es2015' + | 'browser-es2017' + | 'browser-esnext'; /** * output modul format */ @@ -200,7 +214,7 @@ export interface RuntimeConfig { */ namespace?: string; /** - * Whether to isolate the farm entry script, the default is false. + * Whether to isolate the farm entry script, the default is false. * If set to true, the farm entry script will be emitted as a separate file. */ isolate?: boolean; @@ -209,17 +223,17 @@ export interface RuntimeConfig { export interface ScriptConfig { // specify target es version target?: - | 'es3' - | 'es5' - | 'es2015' - | 'es2016' - | 'es2017' - | 'es2018' - | 'es2019' - | 'es2020' - | 'es2021' - | 'es2022' - | 'esnext'; + | 'es3' + | 'es5' + | 'es2015' + | 'es2016' + | 'es2017' + | 'es2018' + | 'es2019' + | 'es2020' + | 'es2021' + | 'es2022' + | 'esnext'; // config swc parser parser?: ScriptParseConfig; decorators?: ScriptDecoratorsConfig; @@ -299,7 +313,7 @@ export interface PersistentCacheConfig { /** @default true */ env?: boolean; }; -}; +} export interface PartialBundlingConfig { /** @@ -356,7 +370,7 @@ export interface PresetEnvConfig { * @see https://babeljs.io/docs/assumptions */ assumptions?: any; -}; +} export interface Config { config?: { @@ -377,7 +391,7 @@ export interface Config { /** * Configure the imports that are external, and the imports that are external will not appear in the compiled product. */ - external?: string[]; + external?: (string | Record)[]; externalNodeBuiltins?: boolean | string[]; mode?: 'development' | 'production'; root?: string; @@ -419,7 +433,7 @@ export interface Config { presetEnv?: boolean | PresetEnvConfig; persistentCache?: boolean | PersistentCacheConfig; comments?: boolean | 'license'; - custom?:Record; + custom?: Record; }; jsPlugins?: JsPlugin[]; // [rustPluginFilePath, jsonStringifiedOptions] diff --git a/packages/core/src/config/constants.ts b/packages/core/src/config/constants.ts index 9bccd5211..e6f570630 100644 --- a/packages/core/src/config/constants.ts +++ b/packages/core/src/config/constants.ts @@ -5,3 +5,8 @@ export const DEFAULT_CONFIG_NAMES = [ ]; export const FARM_DEFAULT_NAMESPACE = 'FARM_DEFAULT_NAMESPACE'; + +export const CUSTOM_KEYS = { + external_record: 'external.record', + runtime_isolate: 'runtime.isolate' +}; diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index afbd370d9..ba1392bc4 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -4,11 +4,7 @@ import module from 'node:module'; import path, { isAbsolute, join } from 'node:path'; import { pathToFileURL } from 'node:url'; -import { - Config, - PluginTransformHookParam, - bindingPath -} from '../../binding/index.js'; +import { PluginTransformHookParam, bindingPath } from '../../binding/index.js'; import { JsPlugin } from '../index.js'; import { getSortedPlugins, @@ -42,14 +38,20 @@ import { normalizeOutput } from './normalize-config/normalize-output.js'; import { normalizePersistentCache } from './normalize-config/normalize-persistent-cache.js'; import { parseUserConfig } from './schema.js'; +import { externalAdapter } from '../plugin/js/external-adapter.js'; import merge from '../utils/merge.js'; -import { DEFAULT_CONFIG_NAMES, FARM_DEFAULT_NAMESPACE } from './constants.js'; +import { + CUSTOM_KEYS, + DEFAULT_CONFIG_NAMES, + FARM_DEFAULT_NAMESPACE +} from './constants.js'; import { mergeConfig, mergeFarmCliConfig } from './mergeConfig.js'; import { normalizeExternal } from './normalize-config/normalize-external.js'; import type { Alias, FarmCLIOptions, NormalizedServerConfig, + ResolvedCompilation, ResolvedUserConfig, UserConfig, UserConfigExport, @@ -88,6 +90,7 @@ async function getDefaultConfig( resolvedUserConfig.compilation = await normalizeUserCompilationConfig( resolvedUserConfig, + config, logger, mode ); @@ -178,7 +181,8 @@ export async function resolveConfig( const sortFarmJsPlugins = getSortedPlugins([ ...rawJsPlugins, - ...vitePluginAdapters + ...vitePluginAdapters, + externalAdapter() ]); const config = await resolveConfigHook(userConfig, sortFarmJsPlugins); @@ -203,6 +207,7 @@ export async function resolveConfig( resolvedUserConfig.compilation = await normalizeUserCompilationConfig( resolvedUserConfig, + mergedUserConfig, logger, mode ); @@ -230,26 +235,35 @@ export async function resolveConfig( return resolvedUserConfig; } -type ServerConfig = { - server?: NormalizedServerConfig; -}; +// type ServerConfig = { +// server?: NormalizedServerConfig; +// }; /** * Normalize user config and transform it to rust compiler compatible config + * + * + * ResolvedUserConfig is a parameter passed to rust Compiler, + * and ResolvedUserConfig is generated from UserConfig. + * When UserConfig is different from ResolvedUserConfig, + * a legal value should be given to the ResolvedUserConfig field here, + * and converted from UserConfig in the subsequent process. + * * @param config * @returns resolved config that parsed to rust compiler */ export async function normalizeUserCompilationConfig( - userConfig: ResolvedUserConfig, + resolvedUserConfig: ResolvedUserConfig, + userConfig: UserConfig, logger: Logger, mode: CompilationMode = 'development' -): Promise { - const { compilation, root } = userConfig; +): Promise { + const { compilation, root } = resolvedUserConfig; // resolve root path const resolvedRootPath = normalizePath(root); - userConfig.root = resolvedRootPath; + resolvedUserConfig.root = resolvedRootPath; // resolve public path if (compilation?.output?.publicPath) { @@ -260,7 +274,8 @@ export async function normalizeUserCompilationConfig( } const inputIndexConfig = checkCompilationInputValue(userConfig, logger); - const config: Config['config'] & ServerConfig = merge( + + const resolvedCompilation: ResolvedCompilation = merge( {}, DEFAULT_COMPILATION_OPTIONS, { @@ -272,47 +287,50 @@ export async function normalizeUserCompilationConfig( const isProduction = mode === 'production'; const isDevelopment = mode === 'development'; - config.mode = config.mode ?? mode; + resolvedCompilation.mode = resolvedCompilation.mode ?? mode; - config.coreLibPath = bindingPath; + resolvedCompilation.coreLibPath = bindingPath; - normalizeOutput(config, isProduction); - normalizeExternal(config); + normalizeOutput(resolvedCompilation, isProduction); + normalizeExternal(userConfig, resolvedCompilation); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore do not check type for this internal option - if (!config.assets?.publicDir) { - if (!config.assets) { - config.assets = {}; + if (!resolvedCompilation.assets?.publicDir) { + if (!resolvedCompilation.assets) { + resolvedCompilation.assets = {}; } - const userPublicDir = userConfig.publicDir - ? userConfig.publicDir - : join(config.root, 'public'); + const userPublicDir = resolvedUserConfig.publicDir + ? resolvedUserConfig.publicDir + : join(resolvedCompilation.root, 'public'); if (isAbsolute(userPublicDir)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore do not check type for this internal option - config.assets.publicDir = userPublicDir; + resolvedCompilation.assets.publicDir = userPublicDir; } else { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore do not check type for this internal option - config.assets.publicDir = join(config.root, userPublicDir); + resolvedCompilation.assets.publicDir = join( + resolvedCompilation.root, + userPublicDir + ); } } - config.define = Object.assign( + resolvedCompilation.define = Object.assign( { // skip self define - ['FARM' + '_PROCESS_ENV']: userConfig.env + ['FARM' + '_PROCESS_ENV']: resolvedUserConfig.env }, - config?.define, + resolvedCompilation?.define, // for node target, we should not define process.env.NODE_ENV - config.output?.targetEnv === 'node' + resolvedCompilation.output?.targetEnv === 'node' ? {} - : Object.keys(userConfig.env || {}).reduce((env: any, key) => { + : Object.keys(resolvedUserConfig.env || {}).reduce((env: any, key) => { env[`$__farm_regex:(global(This)?\\.)?process\\.env\\.${key}`] = - JSON.stringify(userConfig.env[key]); + JSON.stringify(resolvedUserConfig.env[key]); return env; }, {}) ); @@ -323,42 +341,43 @@ export async function normalizeUserCompilationConfig( '@farmfe/runtime-plugin-import-meta' ); - if (!config.runtime) { - config.runtime = { + if (!resolvedCompilation.runtime) { + resolvedCompilation.runtime = { path: require.resolve('@farmfe/runtime'), plugins: [] }; } - if (!config.runtime.path) { - config.runtime.path = require.resolve('@farmfe/runtime'); + if (!resolvedCompilation.runtime.path) { + resolvedCompilation.runtime.path = require.resolve('@farmfe/runtime'); } - if (!config.runtime.swcHelpersPath) { - config.runtime.swcHelpersPath = path.dirname( + if (!resolvedCompilation.runtime.swcHelpersPath) { + resolvedCompilation.runtime.swcHelpersPath = path.dirname( require.resolve('@swc/helpers/package.json') ); } - if (!config.runtime.plugins) { - config.runtime.plugins = []; + if (!resolvedCompilation.runtime.plugins) { + resolvedCompilation.runtime.plugins = []; } else { // make sure all plugin paths are absolute - config.runtime.plugins = config.runtime.plugins.map((plugin) => { - if (!path.isAbsolute(plugin)) { - if (!plugin.startsWith('.')) { - // resolve plugin from node_modules - return require.resolve(plugin); - } else { - return path.resolve(resolvedRootPath, plugin); + resolvedCompilation.runtime.plugins = + resolvedCompilation.runtime.plugins.map((plugin) => { + if (!path.isAbsolute(plugin)) { + if (!plugin.startsWith('.')) { + // resolve plugin from node_modules + return require.resolve(plugin); + } else { + return path.resolve(resolvedRootPath, plugin); + } } - } - return plugin; - }); + return plugin; + }); } // set namespace to package.json name field's hash - if (!config.runtime.namespace) { + if (!resolvedCompilation.runtime.namespace) { // read package.json name field const packageJsonPath = path.resolve(resolvedRootPath, 'package.json'); const packageJsonExists = fs.existsSync(packageJsonPath); @@ -367,62 +386,67 @@ export async function normalizeUserCompilationConfig( ?.name ?? FARM_DEFAULT_NAMESPACE : FARM_DEFAULT_NAMESPACE; - config.runtime.namespace = crypto + resolvedCompilation.runtime.namespace = crypto .createHash('md5') .update(namespaceName) .digest('hex'); } if (isProduction) { - config.lazyCompilation = false; - } else if (config.lazyCompilation === undefined) { + resolvedCompilation.lazyCompilation = false; + } else if (resolvedCompilation.lazyCompilation === undefined) { if (isDevelopment) { - config.lazyCompilation = true; + resolvedCompilation.lazyCompilation = true; } else { - config.lazyCompilation = false; + resolvedCompilation.lazyCompilation = false; } } - if (config.mode === undefined) { - config.mode = mode; + if (resolvedCompilation.mode === undefined) { + resolvedCompilation.mode = mode; } - setProcessEnv(config.mode); + setProcessEnv(resolvedCompilation.mode); // TODO add targetEnv `lib-browser` and `lib-node` support const is_entry_html = - Object.keys(config.input).length === 0 || - Object.values(config.input).some((value) => value.endsWith('.html')); + Object.keys(resolvedCompilation.input).length === 0 || + Object.values(resolvedCompilation.input).some((value) => + value.endsWith('.html') + ); if ( - config.output.targetEnv !== 'node' && - isArray(config.runtime.plugins) && - userConfig.server.hmr && + resolvedCompilation.output.targetEnv !== 'node' && + isArray(resolvedCompilation.runtime.plugins) && + resolvedUserConfig.server?.hmr && is_entry_html && - !config.runtime.plugins.includes(hmrClientPluginPath) + !resolvedCompilation.runtime.plugins.includes(hmrClientPluginPath) ) { - const publicPath = userConfig.compilation?.output?.publicPath ?? '/'; - const hmrPath = userConfig.server.hmr.path; - const serverOptions = userConfig.server; + const publicPath = + resolvedUserConfig.compilation?.output?.publicPath ?? '/'; + const hmrPath = resolvedUserConfig.server.hmr.path; + const serverOptions = resolvedUserConfig.server; const defineHmrPath = normalizeBasePath(path.join(publicPath, hmrPath)); - config.runtime.plugins.push(hmrClientPluginPath); + resolvedCompilation.runtime.plugins.push(hmrClientPluginPath); // TODO optimize get hmr logic - config.define.FARM_HMR_PORT = String( + resolvedCompilation.define.FARM_HMR_PORT = String( (serverOptions.hmr.port || undefined) ?? serverOptions.port ?? DEFAULT_DEV_SERVER_OPTIONS.port ); - config.define.FARM_HMR_HOST = JSON.stringify(userConfig.server.hmr.host); - config.define.FARM_HMR_PROTOCOL = JSON.stringify( - userConfig.server.hmr.protocol + resolvedCompilation.define.FARM_HMR_HOST = JSON.stringify( + resolvedUserConfig.server.hmr.host ); - config.define.FARM_HMR_PATH = JSON.stringify(defineHmrPath); + resolvedCompilation.define.FARM_HMR_PROTOCOL = JSON.stringify( + resolvedUserConfig.server.hmr.protocol + ); + resolvedCompilation.define.FARM_HMR_PATH = JSON.stringify(defineHmrPath); } if ( - isArray(config.runtime.plugins) && - !config.runtime.plugins.includes(ImportMetaPluginPath) + isArray(resolvedCompilation.runtime.plugins) && + !resolvedCompilation.runtime.plugins.includes(ImportMetaPluginPath) ) { - config.runtime.plugins.push(ImportMetaPluginPath); + resolvedCompilation.runtime.plugins.push(ImportMetaPluginPath); } // we should not deep merge compilation.input @@ -438,18 +462,18 @@ export async function normalizeUserCompilationConfig( } } - config.input = input; + resolvedCompilation.input = input; } - if (config.treeShaking === undefined) { + if (resolvedCompilation.treeShaking === undefined) { if (isProduction) { - config.treeShaking = true; + resolvedCompilation.treeShaking = true; } else { - config.treeShaking = false; + resolvedCompilation.treeShaking = false; } } - if (config.script?.plugins?.length) { + if (resolvedCompilation.script?.plugins?.length) { logger.info( `Swc plugins are configured, note that Farm uses ${colors.yellow( 'swc_core v0.90' @@ -464,39 +488,39 @@ export async function normalizeUserCompilationConfig( // lazyCompilation should be disabled in production mode // so, it only happens in development mode // https://github.com/farm-fe/farm/issues/962 - if (config.treeShaking && config.lazyCompilation) { + if (resolvedCompilation.treeShaking && resolvedCompilation.lazyCompilation) { logger.error( 'treeShaking option is not supported in lazyCompilation mode, lazyCompilation will be disabled.' ); - config.lazyCompilation = false; + resolvedCompilation.lazyCompilation = false; } - if (config.minify === undefined) { + if (resolvedCompilation.minify === undefined) { if (isProduction) { - config.minify = true; + resolvedCompilation.minify = true; } else { - config.minify = false; + resolvedCompilation.minify = false; } } - if (config.presetEnv === undefined) { + if (resolvedCompilation.presetEnv === undefined) { if (isProduction) { - config.presetEnv = true; + resolvedCompilation.presetEnv = true; } else { - config.presetEnv = false; + resolvedCompilation.presetEnv = false; } } // setting the custom configuration - config.custom = { - ...(config.custom || {}), - 'runtime.isolate': `${!!config.runtime.isolate}` + resolvedCompilation.custom = { + ...(resolvedCompilation.custom || {}), + [CUSTOM_KEYS.runtime_isolate]: `${!!resolvedCompilation.runtime.isolate}` }; // normalize persistent cache at last - await normalizePersistentCache(config, userConfig); + await normalizePersistentCache(resolvedCompilation, resolvedUserConfig); - return config; + return resolvedCompilation; } export const DEFAULT_HMR_OPTIONS: Required = { @@ -530,7 +554,7 @@ export const DEFAULT_DEV_SERVER_OPTIONS: NormalizedServerConfig = { writeToDisk: false }; -export const DEFAULT_COMPILATION_OPTIONS: Partial = { +export const DEFAULT_COMPILATION_OPTIONS: Partial = { output: { path: './dist', publicPath: '/' @@ -617,38 +641,47 @@ async function readConfigFile( .split('.') .join('')}.mjs`; + const tsDefaultUserConfig: UserConfig = { + root: inlineOptions.root, + compilation: { + input: { + [fileName]: configFilePath + }, + output: { + entryFilename: '[entryName]', + path: outputPath, + format: 'esm', + targetEnv: 'node' + }, + external: ['!^(\\./|\\.\\./|[A-Za-z]:\\\\|/).*'], + partialBundling: { + enforceResources: [ + { + name: fileName, + test: ['.+'] + } + ] + }, + watch: false, + sourcemap: false, + treeShaking: false, + minify: false, + presetEnv: false, + lazyCompilation: false, + persistentCache: false, + progress: false + } + }; + const tsDefaultResolvedUserConfig: ResolvedUserConfig = + await resolveMergedUserConfig( + tsDefaultUserConfig, + undefined, + 'development' + ); + const normalizedConfig = await normalizeUserCompilationConfig( - { - root: inlineOptions.root, - compilation: { - input: { - [fileName]: configFilePath - }, - output: { - entryFilename: '[entryName]', - path: outputPath, - format: 'esm', - targetEnv: 'node' - }, - external: ['!^(\\./|\\.\\./|[A-Za-z]:\\\\|/).*'], - partialBundling: { - enforceResources: [ - { - name: fileName, - test: ['.+'] - } - ] - }, - watch: false, - sourcemap: false, - treeShaking: false, - minify: false, - presetEnv: false, - lazyCompilation: false, - persistentCache: false, - progress: false - } - }, + tsDefaultResolvedUserConfig, + tsDefaultUserConfig, logger, mode as CompilationMode ); @@ -670,7 +703,7 @@ async function readConfigFile( // Change to vm.module of node or loaders as far as it is stable const userConfig = (await import(filePath as string)).default; try { - fs.unlink(filePath, () => void 0); + // fs.unlink(filePath, () => void 0); } catch { /** do nothing */ } @@ -767,8 +800,25 @@ export async function resolveMergedUserConfig( mergedUserConfig: UserConfig, configFilePath: string | undefined, mode: 'development' | 'production' | string -) { - const resolvedUserConfig = { ...mergedUserConfig } as ResolvedUserConfig; +): Promise { + const serverConfig: NormalizedServerConfig = { + ...DEFAULT_DEV_SERVER_OPTIONS, + ...mergedUserConfig.server, + hmr: { + ...DEFAULT_HMR_OPTIONS, + ...(isObject(mergedUserConfig.server?.hmr) + ? mergedUserConfig.server.hmr + : {}) + } + }; + const resolvedUserConfig: ResolvedUserConfig = { + ...mergedUserConfig, + compilation: { + ...mergedUserConfig.compilation, + external: [] + }, + server: serverConfig + }; // set internal config resolvedUserConfig.envMode = mode; diff --git a/packages/core/src/config/normalize-config/normalize-external.ts b/packages/core/src/config/normalize-config/normalize-external.ts index 7d14d3735..8763b1d99 100644 --- a/packages/core/src/config/normalize-config/normalize-external.ts +++ b/packages/core/src/config/normalize-config/normalize-external.ts @@ -3,17 +3,53 @@ import module from 'node:module'; import { existsSync, readFileSync } from 'node:fs'; import path from 'node:path'; import { Config } from '../../../binding/index.js'; +import { safeJsonParse } from '../../utils/json.js'; +import { isObject } from '../../utils/share.js'; +import { CUSTOM_KEYS } from '../constants.js'; +import type { ResolvedCompilation, UserConfig } from '../types.js'; -export function normalizeExternal(config: Config['config']) { +type PartialExternal = [string[], Record]; + +export function partialExternal( + externalConfig: (string | Record)[] = [] +): PartialExternal { + const stringExternal: string[] = []; + const recordExternal: Record = {}; + + /** + * + * `["^node:.*$", { "jquery": "$" }]` + * => + * `["^node:.*$"]` + * `{ "jquery": "$" }` + */ + for (const external of externalConfig) { + if (typeof external === 'string') { + stringExternal.push(external); + } else if (isObject(external)) { + Object.assign(recordExternal, external); + } + } + + return [stringExternal, recordExternal]; +} + +export function normalizeExternal( + config: UserConfig, + resolvedCompilation: ResolvedCompilation +) { const defaultExternals: string[] = []; - const externalNodeBuiltins = config.externalNodeBuiltins ?? true; + const externalNodeBuiltins = config.compilation?.externalNodeBuiltins ?? true; if (externalNodeBuiltins) { if (Array.isArray(externalNodeBuiltins)) { defaultExternals.push(...externalNodeBuiltins); } else if (externalNodeBuiltins === true) { let packageJson: any = {}; - const pkgPath = path.join(config.root || process.cwd(), 'package.json'); + const pkgPath = path.join( + resolvedCompilation.root || process.cwd(), + 'package.json' + ); // the project installed polyfill if (existsSync(pkgPath)) { try { @@ -26,7 +62,7 @@ export function normalizeExternal(config: Config['config']) { defaultExternals.push( ...[...module.builtinModules].filter( (m) => - !config.resolve?.alias?.[m] && + !resolvedCompilation.resolve?.alias?.[m] && !packageJson?.devDependencies?.[m] && !packageJson?.dependencies?.[m] ) @@ -34,9 +70,51 @@ export function normalizeExternal(config: Config['config']) { } } - config.external = [ - ...(config.external ?? []), + if (!config?.compilation?.custom) { + config.compilation.custom = {}; + } + + if (!resolvedCompilation?.custom) { + resolvedCompilation.custom = {}; + } + + const [stringExternal, recordExternal] = mergeCustomExternal( + config.compilation, + mergeCustomExternal( + resolvedCompilation, + partialExternal(config.compilation.external) + ) + ); + + resolvedCompilation.custom[CUSTOM_KEYS.external_record] = + JSON.stringify(recordExternal); + + resolvedCompilation.external = [ + ...stringExternal, '^node:', ...defaultExternals.map((m) => `^${m}($|/promises$)`) ]; } + +export function mergeCustomExternal>( + compilation: T, + external: ReturnType +): PartialExternal { + const [stringExternal, recordExternal] = external; + if (!compilation?.custom) { + compilation.custom = {}; + } + + const oldRecordExternal: Record = compilation.custom[ + CUSTOM_KEYS.external_record + ] + ? safeJsonParse(compilation.custom[CUSTOM_KEYS.external_record], {}) || {} + : {}; + + return [ + [...new Set(stringExternal)], + isObject(oldRecordExternal) + ? { ...oldRecordExternal, ...recordExternal } + : recordExternal + ]; +} diff --git a/packages/core/src/config/normalize-config/normalize-output.ts b/packages/core/src/config/normalize-config/normalize-output.ts index 8a4d4f8e7..af8cd47f1 100644 --- a/packages/core/src/config/normalize-config/normalize-output.ts +++ b/packages/core/src/config/normalize-config/normalize-output.ts @@ -5,9 +5,10 @@ import { FARM_TARGET_BROWSER_ENVS, mapTargetEnvValue } from '../../utils/share.js'; +import { ResolvedCompilation } from '../types.js'; export async function normalizeOutput( - config: Config['config'], + config: ResolvedCompilation, isProduction: boolean ) { if (!config.output.targetEnv) { diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index abf392d53..be8853b6b 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -45,7 +45,9 @@ const compilationConfigSchema = z .strict() .optional(), define: z.record(z.any()).optional(), - external: z.array(z.string()).optional(), + external: z + .array(z.string().or(z.record(z.string(), z.string()))) + .optional(), externalNodeBuiltins: z .union([z.boolean(), z.array(z.string())]) .optional(), @@ -256,7 +258,8 @@ const compilationConfigSchema = z }) .optional() ]), - comments: z.union([z.boolean(), z.literal('license')]).optional() + comments: z.union([z.boolean(), z.literal('license')]).optional(), + custom: z.record(z.string(), z.string()).optional() }) .strict(); diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 37a1fcdda..f7468568c 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -105,6 +105,11 @@ export interface UserConfig { /** Files under this dir will always be treated as static assets. serve it in dev, and copy it to output.path when build */ } +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ResolvedCompilation extends Exclude { + external?: string[]; +} + export interface ResolvedUserConfig extends UserConfig { env?: Record; envDir?: string; @@ -113,7 +118,7 @@ export interface ResolvedUserConfig extends UserConfig { configFilePath?: string; envMode?: string; configFileDependencies?: string[]; - compilation?: Config['config']; + compilation?: ResolvedCompilation; server?: NormalizedServerConfig; jsPlugins?: JsPlugin[]; rustPlugins?: [string, string][]; diff --git a/packages/core/src/plugin/js/external-adapter.ts b/packages/core/src/plugin/js/external-adapter.ts new file mode 100644 index 000000000..8bb990856 --- /dev/null +++ b/packages/core/src/plugin/js/external-adapter.ts @@ -0,0 +1,40 @@ +import { CUSTOM_KEYS } from '../../config/constants.js'; +import { + mergeCustomExternal, + partialExternal +} from '../../config/normalize-config/normalize-external.js'; +import { UserConfig } from '../../config/types.js'; +import { isArray } from '../../utils/share.js'; +import { JsPlugin } from '../type.js'; + +/** + * avoid add new external in config hook + */ +export function externalAdapter(): JsPlugin { + return { + name: 'farm:external-adapter', + + priority: -Infinity, + + config(config: UserConfig): UserConfig | Promise { + if ( + config?.compilation?.external && + isArray(config.compilation.external) + ) { + let [stringExternal, recordExternal] = mergeCustomExternal( + config?.compilation, + partialExternal(config.compilation.external) + ); + + return { + compilation: { + external: stringExternal, + custom: { + [CUSTOM_KEYS.external_record]: JSON.stringify(recordExternal) + } + } + }; + } + } + }; +} diff --git a/packages/core/src/utils/json.ts b/packages/core/src/utils/json.ts new file mode 100644 index 000000000..735399365 --- /dev/null +++ b/packages/core/src/utils/json.ts @@ -0,0 +1,7 @@ +export function safeJsonParse(v: string, defaultValue?: T): T { + try { + return JSON.parse(v); + } catch (error) { + return defaultValue; + } +} diff --git a/packages/core/tests/binding.spec.ts b/packages/core/tests/binding.spec.ts index 2d17d2476..753d31096 100644 --- a/packages/core/tests/binding.spec.ts +++ b/packages/core/tests/binding.spec.ts @@ -5,21 +5,31 @@ import { Compiler, Logger, normalizeDevServerOptions, - normalizeUserCompilationConfig + normalizeUserCompilationConfig, + resolveMergedUserConfig, + UserConfig } from '../src/index.js'; // just make sure the binding works test('Binding - should parse config to rust correctly', async () => { const currentDir = path.dirname(fileURLToPath(import.meta.url)); const serverConfig = normalizeDevServerOptions({}, 'production'); - const compilationConfig = await normalizeUserCompilationConfig( - { - root: path.resolve(currentDir, 'fixtures', 'binding'), - compilation: { - progress: false - }, - server: serverConfig + + const config: UserConfig = { + root: path.resolve(currentDir, 'fixtures', 'binding'), + compilation: { + progress: false }, + server: serverConfig + }; + const resolvedUserConfig = await resolveMergedUserConfig( + config, + undefined, + 'production' + ); + const compilationConfig = await normalizeUserCompilationConfig( + resolvedUserConfig, + config, new Logger() ); const compiler = new Compiler({ diff --git a/packages/core/tests/common.ts b/packages/core/tests/common.ts index 344b33d73..1be503a56 100644 --- a/packages/core/tests/common.ts +++ b/packages/core/tests/common.ts @@ -1,9 +1,13 @@ import { fileURLToPath } from 'node:url'; import path from 'path'; import { Compiler } from '../src/compiler/index.js'; -import { normalizeUserCompilationConfig } from '../src/config/index.js'; -import { Logger } from '../src/index.js'; import { JsPlugin } from '../src/plugin/type.js'; +import { + normalizeUserCompilationConfig, + resolveMergedUserConfig, + UserConfig +} from '../src/config/index.js'; +import { Logger } from '../src/index.js'; export async function getCompiler( root: string, @@ -18,26 +22,34 @@ export async function getCompiler( return originalExit(code); }; - const compilationConfig = await normalizeUserCompilationConfig( - { - root, - compilation: { - input: input ?? { - index: './index.ts?foo=bar' // Farm does not recommand using query strings in input. We just use it for testing. - }, - output: { - path: path.join('dist', p), - entryFilename: '[entryName].mjs', - targetEnv: 'node', - ...(output ?? {}) - }, - progress: false, - lazyCompilation: false, - sourcemap: false, - persistentCache: false + const userConfig: UserConfig = { + root, + compilation: { + input: input ?? { + index: './index.ts?foo=bar' // Farm does not recommand using query strings in input. We just use it for testing. }, - plugins + output: { + path: path.join('dist', p), + entryFilename: '[entryName].mjs', + targetEnv: 'node', + ...(output ?? {}) + }, + progress: false, + lazyCompilation: false, + sourcemap: false, + persistentCache: false }, + plugins + }; + const resolvedUserConfig = await resolveMergedUserConfig( + userConfig, + undefined, + 'production' + ); + + const compilationConfig = await normalizeUserCompilationConfig( + resolvedUserConfig, + userConfig, new Logger(), 'production' ); diff --git a/packages/core/tests/js-plugin-hooks/__snapshots__/transform-html.spec.ts.snap b/packages/core/tests/js-plugin-hooks/__snapshots__/transform-html.spec.ts.snap index d1959d84c..ca17dbaff 100644 --- a/packages/core/tests/js-plugin-hooks/__snapshots__/transform-html.spec.ts.snap +++ b/packages/core/tests/js-plugin-hooks/__snapshots__/transform-html.spec.ts.snap @@ -5,5 +5,5 @@ exports[`Js Plugin Execution - transformHtml 1`] = ` (globalThis || window || global)['f081367f80fe14896375a9f8b8918ca3'] = {}; (globalThis || window || global)['f081367f80fe14896375a9f8b8918ca3'] = { __FARM_TARGET_ENV__: 'browser', -};
{ssr}
" +};
{ssr}
" `; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba158163b..c5a7ae96a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -304,6 +304,21 @@ importers: specifier: workspace:* version: link:../../packages/core + examples/external: + devDependencies: + '@farmfe/cli': + specifier: ^1.0.1 + version: link:../../packages/cli + '@farmfe/core': + specifier: workspace:* + version: link:../../packages/core + '@types/jquery': + specifier: ^3.5.29 + version: 3.5.29 + typescript: + specifier: ^5.4.3 + version: 5.4.5 + examples/generate-dts: devDependencies: '@farmfe/cli': @@ -1537,6 +1552,9 @@ importers: ts-morph: specifier: ^19.0.0 version: 19.0.0 + typescript: + specifier: ^5.4.5 + version: 5.4.5 devDependencies: '@farmfe/cli': specifier: workspace:* @@ -3518,14 +3536,14 @@ packages: '@commitlint/types': 17.8.1 '@types/node': 20.5.1 chalk: 4.1.2 - cosmiconfig: 8.3.6(typescript@5.2.2) - cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6)(ts-node@10.9.1)(typescript@5.2.2) + cosmiconfig: 8.3.6(typescript@5.4.5) + cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6)(ts-node@10.9.1)(typescript@5.4.5) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1(@types/node@20.5.1)(typescript@5.2.2) - typescript: 5.2.2 + ts-node: 10.9.1(@types/node@20.5.1)(typescript@5.4.5) + typescript: 5.4.5 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' @@ -6079,6 +6097,16 @@ packages: resolution: {integrity: sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==} dev: true + /@types/jquery@3.5.29: + resolution: {integrity: sha512-oXQQC9X9MOPRrMhPHHOsXqeQDnWeCDT3PelUIg/Oy8FAbzSZtFHRjc7IpbfFVmpLtJ+UOoywpRsuO5Jxjybyeg==} + dependencies: + '@types/sizzle': 2.3.8 + dev: true + + /@types/json-schema@7.0.14: + resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==} + dev: true + /@types/jsonfile@6.1.3: resolution: {integrity: sha512-/yqTk2SZ1wIezK0hiRZD7RuSf4B3whFxFamB1kGStv+8zlWScTMcHanzfc0XKWs5vA1TkHeckBlOyM8jxU8nHA==} dependencies: @@ -6293,6 +6321,10 @@ packages: '@types/node': 18.18.8 dev: true + /@types/sizzle@2.3.8: + resolution: {integrity: sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==} + dev: true + /@types/sortablejs@1.15.8: resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==} dev: false @@ -8811,7 +8843,7 @@ packages: vary: 1.1.2 dev: true - /cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6)(ts-node@10.9.1)(typescript@5.2.2): + /cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6)(ts-node@10.9.1)(typescript@5.4.5): resolution: {integrity: sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==} engines: {node: '>=v14.21.3'} peerDependencies: @@ -8821,9 +8853,9 @@ packages: typescript: '>=4' dependencies: '@types/node': 20.5.1 - cosmiconfig: 8.3.6(typescript@5.2.2) - ts-node: 10.9.1(@types/node@20.5.1)(typescript@5.2.2) - typescript: 5.2.2 + cosmiconfig: 8.3.6(typescript@5.4.5) + ts-node: 10.9.1(@types/node@20.5.1)(typescript@5.4.5) + typescript: 5.4.5 dev: true /cosmiconfig@7.1.0: @@ -8863,7 +8895,7 @@ packages: typescript: 4.9.5 dev: false - /cosmiconfig@8.3.6(typescript@5.2.2): + /cosmiconfig@8.3.6(typescript@5.4.5): resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} peerDependencies: @@ -8876,7 +8908,7 @@ packages: js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 - typescript: 5.2.2 + typescript: 5.4.5 dev: true /crc-32@1.2.2: @@ -15379,8 +15411,8 @@ packages: picocolors: 1.0.0 sade: 1.8.1 svelte: 4.0.0 - svelte-preprocess: 5.1.3(svelte@4.0.0)(typescript@5.2.2) - typescript: 5.2.2 + svelte-preprocess: 5.1.3(svelte@4.0.0)(typescript@5.4.5) + typescript: 5.4.5 transitivePeerDependencies: - '@babel/core' - coffeescript @@ -15402,7 +15434,7 @@ packages: svelte: 4.0.0 dev: true - /svelte-preprocess@5.1.3(svelte@4.0.0)(typescript@5.2.2): + /svelte-preprocess@5.1.3(svelte@4.0.0)(typescript@5.4.5): resolution: {integrity: sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==} engines: {node: '>= 16.0.0', pnpm: ^8.0.0} requiresBuild: true @@ -15446,7 +15478,7 @@ packages: sorcery: 0.11.0 strip-indent: 3.0.0 svelte: 4.0.0 - typescript: 5.2.2 + typescript: 5.4.5 dev: true /svelte@4.0.0: @@ -15824,7 +15856,7 @@ packages: code-block-writer: 12.0.0 dev: false - /ts-node@10.9.1(@types/node@20.5.1)(typescript@5.2.2): + /ts-node@10.9.1(@types/node@20.5.1)(typescript@5.4.5): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -15850,7 +15882,7 @@ packages: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.2.2 + typescript: 5.4.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 dev: true @@ -15995,6 +16027,11 @@ packages: hasBin: true dev: true + /typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} + hasBin: true + /ua-parser-js@1.0.37: resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==} dev: false