mirror of
				https://github.com/NativeScript/NativeScript.git
				synced 2025-10-31 16:06:01 +08:00 
			
		
		
		
	feat(webpack): support es module bundling (#10788)
This commit is contained in:
		| @ -23,8 +23,8 @@ export function test_global_registerModule() { | |||||||
| 	TKUnit.assert(typeof global.registerModule === 'function', 'global.registerModule not a function'); | 	TKUnit.assert(typeof global.registerModule === 'function', 'global.registerModule not a function'); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function test_global_registerWebpackModules() { | export function test_global_registerBundlerModules() { | ||||||
| 	TKUnit.assert(typeof global.registerWebpackModules === 'function', 'global.registerWebpackModules not a function'); | 	TKUnit.assert(typeof global.registerBundlerModules === 'function', 'global.registerBundlerModules not a function'); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function test_global_loadModule() { | export function test_global_loadModule() { | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								packages/core/global-types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								packages/core/global-types.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -53,11 +53,11 @@ declare module globalThis { | |||||||
|  |  | ||||||
| 	function registerModule(name: string, loader: (name: string) => any): void; | 	function registerModule(name: string, loader: (name: string) => any): void; | ||||||
| 	/** | 	/** | ||||||
| 	 * Register all modules from a webpack context. | 	 * Register all modules from a bundler context. | ||||||
| 	 * The context is one created using the following webpack utility: | 	 * For example, the context could be one created using the following webpack utility: | ||||||
| 	 * https://webpack.js.org/guides/dependency-management/#requirecontext | 	 * https://webpack.js.org/guides/dependency-management/#requirecontext | ||||||
| 	 * | 	 * | ||||||
| 	 * The extension map is optional, modules in the webpack context will have their original file extension (e.g. may be ".ts" or ".scss" etc.), | 	 * The extension map is optional, modules in the bundler context will have their original file extension (e.g. may be ".ts" or ".scss" etc.), | ||||||
| 	 * while the built-in module builders in {N} will look for ".js", ".css" or ".xml" files. Adding a map such as: | 	 * while the built-in module builders in {N} will look for ".js", ".css" or ".xml" files. Adding a map such as: | ||||||
| 	 * ``` | 	 * ``` | ||||||
| 	 * { ".ts": ".js" } | 	 * { ".ts": ".js" } | ||||||
| @ -65,7 +65,7 @@ declare module globalThis { | |||||||
| 	 * Will resolve lookups for .js to the .ts file. | 	 * Will resolve lookups for .js to the .ts file. | ||||||
| 	 * By default scss and ts files are mapped. | 	 * By default scss and ts files are mapped. | ||||||
| 	 */ | 	 */ | ||||||
| 	function registerWebpackModules(context: { keys(): string[]; (key: string): any }, extensionMap?: { [originalFileExtension: string]: string }); | 	function registerBundlerModules(context: { keys(): string[]; (key: string): any }, extensionMap?: { [originalFileExtension: string]: string }); | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * The NativeScript XML builder, style-scope, application modules use various resources such as: | 	 * The NativeScript XML builder, style-scope, application modules use various resources such as: | ||||||
| @ -91,7 +91,7 @@ declare module globalThis { | |||||||
| 	function loadModule(name: string, loadForUI?: boolean): any; | 	function loadModule(name: string, loadForUI?: boolean): any; | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Checks if the module has been registered with `registerModule` or in `registerWebpackModules` | 	 * Checks if the module has been registered with `registerModule` or in `registerBundlerModules` | ||||||
| 	 * @param name Name of the module | 	 * @param name Name of the module | ||||||
| 	 */ | 	 */ | ||||||
| 	function moduleExists(name: string): boolean; | 	function moduleExists(name: string): boolean; | ||||||
| @ -100,8 +100,6 @@ declare module globalThis { | |||||||
|  |  | ||||||
| 	function _unregisterModule(name: string): void; | 	function _unregisterModule(name: string): void; | ||||||
|  |  | ||||||
| 	function _isModuleLoadedForUI(moduleName: string): boolean; |  | ||||||
|  |  | ||||||
| 	var onGlobalLayoutListener: any; | 	var onGlobalLayoutListener: any; | ||||||
| 	function zonedCallback<T = Function>(callback: T): T; | 	function zonedCallback<T = Function>(callback: T): T; | ||||||
| 	var Reflect: any; | 	var Reflect: any; | ||||||
|  | |||||||
| @ -170,15 +170,20 @@ export function initGlobal() { | |||||||
| 			modules.delete(name); | 			modules.delete(name); | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		global._isModuleLoadedForUI = function _isModuleLoadedForUI(moduleName: string): boolean { | 		global.registerBundlerModules = function registerBundlerModules(context: Context, extensionMap: ExtensionMap = {}) { | ||||||
| 			return modulesLoadedForUI.has(moduleName); | 			const registerWithName = (nickName: string, moduleId: string) => { | ||||||
| 		}; | 				modules.set(nickName, { | ||||||
|  | 					moduleId, | ||||||
|  | 					loader: () => { | ||||||
|  | 						return context(moduleId); | ||||||
|  | 					}, | ||||||
|  | 				}); | ||||||
|  | 			}; | ||||||
|  |  | ||||||
| 		global.registerWebpackModules = function registerWebpackModules(context: Context, extensionMap: ExtensionMap = {}) { | 			const registerModuleById = (moduleId: string) => { | ||||||
| 			context.keys().forEach((moduleId) => { |  | ||||||
| 				const extDotIndex = moduleId.lastIndexOf('.'); | 				const extDotIndex = moduleId.lastIndexOf('.'); | ||||||
| 				const base = moduleId.substr(0, extDotIndex); | 				const base = moduleId.substring(0, extDotIndex); | ||||||
| 				const originalExt = moduleId.substr(extDotIndex); | 				const originalExt = moduleId.substring(extDotIndex); | ||||||
| 				const registerExt = extensionMap[originalExt] || defaultExtensionMap[originalExt] || originalExt; | 				const registerExt = extensionMap[originalExt] || defaultExtensionMap[originalExt] || originalExt; | ||||||
|  |  | ||||||
| 				// We prefer source files for webpack scenarios before compilation leftovers, | 				// We prefer source files for webpack scenarios before compilation leftovers, | ||||||
| @ -187,47 +192,40 @@ export function initGlobal() { | |||||||
| 				const isSourceFile = originalExt !== registerExt; | 				const isSourceFile = originalExt !== registerExt; | ||||||
| 				const registerName = base + registerExt; | 				const registerName = base + registerExt; | ||||||
|  |  | ||||||
| 				const registerWithName = (nickName: string) => { |  | ||||||
| 					modules.set(nickName, { |  | ||||||
| 						moduleId, |  | ||||||
| 						loader: () => { |  | ||||||
| 							return context(moduleId); |  | ||||||
| 						}, |  | ||||||
| 					}); |  | ||||||
| 				}; |  | ||||||
|  |  | ||||||
| 				if (registerName.startsWith('./') && registerName.endsWith('.js')) { | 				if (registerName.startsWith('./') && registerName.endsWith('.js')) { | ||||||
| 					const jsNickNames = [ | 					const jsNickNames = [ | ||||||
| 						// This is extremely short version like "main-page" that was promoted to be used with global.registerModule("module-name", loaderFunc); | 						// This is extremely short version like "main-page" that was promoted to be used with global.registerModule("module-name", loaderFunc); | ||||||
| 						registerName.substr(2, registerName.length - 5), | 						registerName.substring(2, registerName.length - 3), | ||||||
| 						// This is for supporting module names like "./main/main-page" | 						// This is for supporting module names like "./main/main-page" | ||||||
| 						registerName.substr(0, registerName.length - 3), | 						registerName.substring(0, registerName.length - 3), | ||||||
| 						// This is for supporting module names like "main/main-page.js" | 						// This is for supporting module names like "main/main-page.js" | ||||||
| 						registerName.substr(2), | 						registerName.substring(2), | ||||||
| 					]; | 					]; | ||||||
|  |  | ||||||
| 					jsNickNames.forEach((jsNickName) => { | 					jsNickNames.forEach((jsNickName) => { | ||||||
| 						if (isSourceFile || !global.moduleExists(jsNickName)) { | 						if (isSourceFile || !global.moduleExists(jsNickName)) { | ||||||
| 							registerWithName(jsNickName); | 							registerWithName(jsNickName, moduleId); | ||||||
| 						} | 						} | ||||||
| 					}); | 					}); | ||||||
| 				} else if (registerName.startsWith('./')) { | 				} else if (registerName.startsWith('./')) { | ||||||
| 					const moduleNickNames = [ | 					const moduleNickNames = [ | ||||||
| 						// This is for supporting module names like "main/main-page.xml" | 						// This is for supporting module names like "main/main-page.xml" | ||||||
| 						registerName.substr(2), | 						registerName.substring(2), | ||||||
| 					]; | 					]; | ||||||
|  |  | ||||||
| 					moduleNickNames.forEach((moduleNickName) => { | 					moduleNickNames.forEach((moduleNickName) => { | ||||||
| 						if (!global.moduleExists(moduleNickName)) { | 						if (!global.moduleExists(moduleNickName)) { | ||||||
| 							registerWithName(moduleNickName); | 							registerWithName(moduleNickName, moduleId); | ||||||
| 						} | 						} | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if (isSourceFile || !global.moduleExists(registerName)) { | 				if (isSourceFile || !global.moduleExists(registerName)) { | ||||||
| 					registerWithName(registerName); | 					registerWithName(registerName, moduleId); | ||||||
| 				} | 				} | ||||||
| 			}); | 			}; | ||||||
|  |  | ||||||
|  | 			context.keys().forEach(registerModuleById); | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		global.moduleExists = function moduleExists(name: string): boolean { | 		global.moduleExists = function moduleExists(name: string): boolean { | ||||||
|  | |||||||
							
								
								
									
										515
									
								
								packages/webpack5/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										515
									
								
								packages/webpack5/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
| 	"name": "@nativescript/webpack", | 	"name": "@nativescript/webpack", | ||||||
| 	"version": "5.0.24", | 	"version": "5.0.25-alpha.1", | ||||||
| 	"private": false, | 	"private": false, | ||||||
| 	"main": "dist/index.js", | 	"main": "dist/index.js", | ||||||
| 	"files": [ | 	"files": [ | ||||||
|  | |||||||
| @ -23,7 +23,7 @@ | |||||||
| 				"assets": [ | 				"assets": [ | ||||||
| 					{ | 					{ | ||||||
| 						"input": "{projectRoot}/src/stubs", | 						"input": "{projectRoot}/src/stubs", | ||||||
| 						"glob": "*.js", | 						"glob": "*.{js,mjs}", | ||||||
| 						"output": "stubs" | 						"output": "stubs" | ||||||
| 					}, | 					}, | ||||||
| 					{ | 					{ | ||||||
|  | |||||||
| @ -1,11 +1,8 @@ | |||||||
| import { extname, relative, resolve } from 'path'; | import { extname, relative, resolve } from 'path'; | ||||||
| import { | import { ContextExclusionPlugin, HotModuleReplacementPlugin } from 'webpack'; | ||||||
| 	ContextExclusionPlugin, |  | ||||||
| 	DefinePlugin, |  | ||||||
| 	HotModuleReplacementPlugin, |  | ||||||
| } from 'webpack'; |  | ||||||
| import Config from 'webpack-chain'; | import Config from 'webpack-chain'; | ||||||
| import { satisfies } from 'semver'; | import { satisfies } from 'semver'; | ||||||
|  | import { isVersionGteConsideringPrerelease } from '../helpers/dependencies'; | ||||||
| import { existsSync } from 'fs'; | import { existsSync } from 'fs'; | ||||||
|  |  | ||||||
| import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; | import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; | ||||||
| @ -13,11 +10,16 @@ import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; | |||||||
| import TerserPlugin from 'terser-webpack-plugin'; | import TerserPlugin from 'terser-webpack-plugin'; | ||||||
|  |  | ||||||
| import { getProjectFilePath, getProjectTSConfigPath } from '../helpers/project'; | import { getProjectFilePath, getProjectTSConfigPath } from '../helpers/project'; | ||||||
| import { getDependencyVersion, hasDependency } from '../helpers/dependencies'; | import { | ||||||
|  | 	getDependencyVersion, | ||||||
|  | 	hasDependency, | ||||||
|  | 	getResolvedDependencyVersionForCheck, | ||||||
|  | } from '../helpers/dependencies'; | ||||||
| import { PlatformSuffixPlugin } from '../plugins/PlatformSuffixPlugin'; | import { PlatformSuffixPlugin } from '../plugins/PlatformSuffixPlugin'; | ||||||
| import { applyFileReplacements } from '../helpers/fileReplacements'; | import { applyFileReplacements } from '../helpers/fileReplacements'; | ||||||
| import { addCopyRule, applyCopyRules } from '../helpers/copyRules'; | import { addCopyRule, applyCopyRules } from '../helpers/copyRules'; | ||||||
| import { WatchStatePlugin } from '../plugins/WatchStatePlugin'; | import { WatchStatePlugin } from '../plugins/WatchStatePlugin'; | ||||||
|  | import { CompatDefinePlugin } from '../plugins/CompatDefinePlugin'; | ||||||
| import { applyDotEnvPlugin } from '../helpers/dotEnv'; | import { applyDotEnvPlugin } from '../helpers/dotEnv'; | ||||||
| import { env as _env, IWebpackEnv } from '../index'; | import { env as _env, IWebpackEnv } from '../index'; | ||||||
| import { getValue } from '../helpers/config'; | import { getValue } from '../helpers/config'; | ||||||
| @ -39,6 +41,64 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { | |||||||
| 	// set mode | 	// set mode | ||||||
| 	config.mode(mode); | 	config.mode(mode); | ||||||
|  |  | ||||||
|  | 	// use source map files by default with v9+ | ||||||
|  | 	function useSourceMapFiles() { | ||||||
|  | 		if (mode === 'development') { | ||||||
|  | 			// in development we always use source-map files with v9+ runtimes | ||||||
|  | 			// they are parsed and mapped to display in-flight app error screens | ||||||
|  | 			env.sourceMap = 'source-map'; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// determine target output by @nativescript/* runtime version | ||||||
|  | 	// v9+ supports ESM output, anything below uses CommonJS | ||||||
|  | 	if ( | ||||||
|  | 		hasDependency('@nativescript/ios') || | ||||||
|  | 		hasDependency('@nativescript/visionos') || | ||||||
|  | 		hasDependency('@nativescript/android') | ||||||
|  | 	) { | ||||||
|  | 		const iosVersion = getDependencyVersion('@nativescript/ios'); | ||||||
|  | 		const visionosVersion = getDependencyVersion('@nativescript/visionos'); | ||||||
|  | 		const androidVersion = getDependencyVersion('@nativescript/android'); | ||||||
|  |  | ||||||
|  | 		if (platform === 'ios') { | ||||||
|  | 			const iosResolved = | ||||||
|  | 				getResolvedDependencyVersionForCheck('@nativescript/ios', '9.0.0') ?? | ||||||
|  | 				iosVersion ?? | ||||||
|  | 				undefined; | ||||||
|  | 			if (isVersionGteConsideringPrerelease(iosResolved, '9.0.0')) { | ||||||
|  | 				useSourceMapFiles(); | ||||||
|  | 			} else { | ||||||
|  | 				env.commonjs = true; | ||||||
|  | 			} | ||||||
|  | 		} else if (platform === 'visionos') { | ||||||
|  | 			const visionosResolved = | ||||||
|  | 				getResolvedDependencyVersionForCheck( | ||||||
|  | 					'@nativescript/visionos', | ||||||
|  | 					'9.0.0', | ||||||
|  | 				) ?? | ||||||
|  | 				visionosVersion ?? | ||||||
|  | 				undefined; | ||||||
|  | 			if (isVersionGteConsideringPrerelease(visionosResolved, '9.0.0')) { | ||||||
|  | 				useSourceMapFiles(); | ||||||
|  | 			} else { | ||||||
|  | 				env.commonjs = true; | ||||||
|  | 			} | ||||||
|  | 		} else if (platform === 'android') { | ||||||
|  | 			const androidResolved = | ||||||
|  | 				getResolvedDependencyVersionForCheck( | ||||||
|  | 					'@nativescript/android', | ||||||
|  | 					'9.0.0', | ||||||
|  | 				) ?? | ||||||
|  | 				androidVersion ?? | ||||||
|  | 				undefined; | ||||||
|  | 			if (isVersionGteConsideringPrerelease(androidResolved, '9.0.0')) { | ||||||
|  | 				useSourceMapFiles(); | ||||||
|  | 			} else { | ||||||
|  | 				env.commonjs = true; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// config.stats({ | 	// config.stats({ | ||||||
| 	// 	logging: 'verbose' | 	// 	logging: 'verbose' | ||||||
| 	// }) | 	// }) | ||||||
| @ -57,6 +117,37 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { | |||||||
| 		node: false, | 		node: false, | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|  | 	// Mock Node.js built-ins that are not available in NativeScript runtime | ||||||
|  | 	// but are required by some packages like css-tree | ||||||
|  | 	config.resolve.merge({ | ||||||
|  | 		fallback: { | ||||||
|  | 			module: require.resolve('../polyfills/module.js'), | ||||||
|  | 		}, | ||||||
|  | 		alias: { | ||||||
|  | 			// Mock mdn-data modules that css-tree tries to load | ||||||
|  | 			'mdn-data/css/properties.json': require.resolve( | ||||||
|  | 				'../polyfills/mdn-data-properties.js', | ||||||
|  | 			), | ||||||
|  | 			'mdn-data/css/syntaxes.json': require.resolve( | ||||||
|  | 				'../polyfills/mdn-data-syntaxes.js', | ||||||
|  | 			), | ||||||
|  | 			'mdn-data/css/at-rules.json': require.resolve( | ||||||
|  | 				'../polyfills/mdn-data-at-rules.js', | ||||||
|  | 			), | ||||||
|  | 			// Ensure imports of the Node 'module' builtin resolve to our polyfill | ||||||
|  | 			module: require.resolve('../polyfills/module.js'), | ||||||
|  | 		}, | ||||||
|  | 		// Allow extension-less ESM imports (fixes "fully specified" errors) | ||||||
|  | 		// Example: '../timer' -> resolves to index.<platform>.js without requiring explicit extension | ||||||
|  | 		fullySpecified: false, | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// As an extra guard, ensure rule-level resolve also allows extension-less imports | ||||||
|  | 	config.module | ||||||
|  | 		.rule('esm-extensionless') | ||||||
|  | 		.test(/\.(mjs|js|ts|tsx)$/) | ||||||
|  | 		.resolve.set('fullySpecified', false); | ||||||
|  |  | ||||||
| 	const getSourceMapType = (map: string | boolean): Config.DevTool => { | 	const getSourceMapType = (map: string | boolean): Config.DevTool => { | ||||||
| 		const defaultSourceMap = 'inline-source-map'; | 		const defaultSourceMap = 'inline-source-map'; | ||||||
|  |  | ||||||
| @ -98,6 +189,8 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { | |||||||
| 	// appears to be working - but we still have to deal with HMR | 	// appears to be working - but we still have to deal with HMR | ||||||
| 	config.target('node'); | 	config.target('node'); | ||||||
|  |  | ||||||
|  | 	// config.entry('globals').add('@nativescript/core/globals/index').end(); | ||||||
|  |  | ||||||
| 	config | 	config | ||||||
| 		.entry('bundle') | 		.entry('bundle') | ||||||
| 		// ensure we load nativescript globals first | 		// ensure we load nativescript globals first | ||||||
| @ -124,16 +217,38 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { | |||||||
| 			.add('@nativescript/core/inspector_modules'); | 			.add('@nativescript/core/inspector_modules'); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	config.output | 	if (env.commonjs) { | ||||||
| 		.path(outputPath) | 		// CommonJS output | ||||||
| 		.pathinfo(false) | 		config.output | ||||||
| 		.publicPath('') | 			.path(outputPath) | ||||||
| 		.libraryTarget('commonjs') | 			.pathinfo(false) | ||||||
| 		.globalObject('global') | 			.publicPath('') | ||||||
| 		.set('clean', true); | 			.libraryTarget('commonjs') | ||||||
|  | 			.globalObject('global') | ||||||
|  | 			.set('clean', true); | ||||||
|  | 		if (env === null || env === void 0 ? void 0 : env.uniqueBundle) { | ||||||
|  | 			config.output.filename(`[name].${env.uniqueBundle}.js`); | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		// ESM output | ||||||
|  | 		config.merge({ | ||||||
|  | 			experiments: { | ||||||
|  | 				// enable ES module syntax (import/exports) | ||||||
|  | 				outputModule: true, | ||||||
|  | 			}, | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 	if (env?.uniqueBundle) { | 		config.output | ||||||
| 		config.output.filename(`[name].${env.uniqueBundle}.js`); | 			.path(outputPath) | ||||||
|  | 			.pathinfo(false) | ||||||
|  | 			.publicPath('file:///app/') | ||||||
|  | 			.set('module', true) | ||||||
|  | 			.libraryTarget('module') | ||||||
|  | 			.globalObject('global') | ||||||
|  | 			.set('clean', true); | ||||||
|  | 		if (env === null || env === void 0 ? void 0 : env.uniqueBundle) { | ||||||
|  | 			config.output.filename(`[name].${env.uniqueBundle}.mjs`); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	config.watchOptions({ | 	config.watchOptions({ | ||||||
| @ -175,16 +290,43 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { | |||||||
|  |  | ||||||
| 	config.optimization.runtimeChunk('single'); | 	config.optimization.runtimeChunk('single'); | ||||||
|  |  | ||||||
| 	config.optimization.splitChunks({ | 	if (env.commonjs) { | ||||||
| 		cacheGroups: { | 		// Set up CommonJS output | ||||||
| 			defaultVendor: { | 		config.optimization.splitChunks({ | ||||||
| 				test: /[\\/]node_modules[\\/]/, | 			cacheGroups: { | ||||||
| 				priority: -10, | 				defaultVendor: { | ||||||
| 				name: 'vendor', | 					test: /[\\/]node_modules[\\/]/, | ||||||
| 				chunks: 'all', | 					priority: -10, | ||||||
|  | 					name: 'vendor', | ||||||
|  | 					chunks: 'all', | ||||||
|  | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}); | ||||||
| 	}); | 	} else { | ||||||
|  | 		// Set up ESM output | ||||||
|  | 		config.output.chunkFilename('[name].mjs'); | ||||||
|  |  | ||||||
|  | 		// now re‑add exactly what you want: | ||||||
|  | 		config.optimization.splitChunks({ | ||||||
|  | 			// only split out vendor from the main bundle… | ||||||
|  | 			chunks: 'initial', | ||||||
|  | 			cacheGroups: { | ||||||
|  | 				// no “default” group | ||||||
|  | 				default: false, | ||||||
|  |  | ||||||
|  | 				// only pull node_modules into vendor.js from the *initial* chunk | ||||||
|  | 				vendor: { | ||||||
|  | 					test: /[\\/]node_modules[\\/]/, | ||||||
|  | 					name: 'vendor', | ||||||
|  | 					chunks: 'initial', | ||||||
|  | 					priority: -10, | ||||||
|  | 					reuseExistingChunk: true, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		config.optimization.set('moduleIds', 'named').set('chunkIds', 'named'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// look for loaders in | 	// look for loaders in | ||||||
| 	//  - node_modules/@nativescript/webpack/dist/loaders | 	//  - node_modules/@nativescript/webpack/dist/loaders | ||||||
| @ -407,7 +549,14 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { | |||||||
| 		.options(postCSSOptions) | 		.options(postCSSOptions) | ||||||
| 		.end() | 		.end() | ||||||
| 		.use('sass-loader') | 		.use('sass-loader') | ||||||
| 		.loader('sass-loader'); | 		.loader('sass-loader') | ||||||
|  | 		.options({ | ||||||
|  | 			// helps ensure proper project compatibility | ||||||
|  | 			// particularly in cases of workspaces | ||||||
|  | 			// which may have different nested Sass implementations | ||||||
|  | 			// via transient dependencies | ||||||
|  | 			implementation: require('sass'), | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 	// config.plugin('NormalModuleReplacementPlugin').use(NormalModuleReplacementPlugin, [ | 	// config.plugin('NormalModuleReplacementPlugin').use(NormalModuleReplacementPlugin, [ | ||||||
| 	// 	/.*/, | 	// 	/.*/, | ||||||
| @ -440,7 +589,7 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { | |||||||
| 	config | 	config | ||||||
| 		.plugin('ContextExclusionPlugin|Other_Platforms') | 		.plugin('ContextExclusionPlugin|Other_Platforms') | ||||||
| 		.use(ContextExclusionPlugin, [ | 		.use(ContextExclusionPlugin, [ | ||||||
| 			new RegExp(`\\.(${otherPlatformsRE})\\.(\\w+)$`), | 			new RegExp(`\.(${otherPlatformsRE})\.(\w+)$`), | ||||||
| 		]); | 		]); | ||||||
|  |  | ||||||
| 	// Filter common undesirable warnings | 	// Filter common undesirable warnings | ||||||
| @ -460,30 +609,31 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { | |||||||
| 	); | 	); | ||||||
|  |  | ||||||
| 	// todo: refine defaults | 	// todo: refine defaults | ||||||
| 	config.plugin('DefinePlugin').use(DefinePlugin, [ | 	config.plugin('DefinePlugin').use( | ||||||
| 		{ | 		CompatDefinePlugin as any, | ||||||
| 			__DEV__: mode === 'development', | 		[ | ||||||
| 			__NS_WEBPACK__: true, | 			{ | ||||||
| 			__NS_ENV_VERBOSE__: !!env.verbose, | 				__DEV__: mode === 'development', | ||||||
| 			__NS_DEV_HOST_IPS__: | 				__NS_WEBPACK__: true, | ||||||
| 				mode === 'development' ? JSON.stringify(getIPS()) : `[]`, | 				__NS_ENV_VERBOSE__: !!env.verbose, | ||||||
| 			__CSS_PARSER__: JSON.stringify(getValue('cssParser', 'css-tree')), | 				__NS_DEV_HOST_IPS__: | ||||||
| 			__UI_USE_XML_PARSER__: true, | 					mode === 'development' ? JSON.stringify(getIPS()) : `[]`, | ||||||
| 			__UI_USE_EXTERNAL_RENDERER__: false, | 				__CSS_PARSER__: JSON.stringify(getValue('cssParser', 'css-tree')), | ||||||
| 			__ANDROID__: platform === 'android', | 				__UI_USE_XML_PARSER__: true, | ||||||
| 			__IOS__: platform === 'ios', | 				__UI_USE_EXTERNAL_RENDERER__: false, | ||||||
| 			__VISIONOS__: platform === 'visionos', | 				__COMMONJS__: !!env.commonjs, | ||||||
| 			__APPLE__: platform === 'ios' || platform === 'visionos', | 				__ANDROID__: platform === 'android', | ||||||
| 			/* for compat only */ 'global.isAndroid': platform === 'android', | 				__IOS__: platform === 'ios', | ||||||
| 			/* for compat only */ 'global.isIOS': | 				__VISIONOS__: platform === 'visionos', | ||||||
| 				platform === 'ios' || platform === 'visionos', | 				__APPLE__: platform === 'ios' || platform === 'visionos', | ||||||
| 			/* for compat only */ 'global.isVisionOS': platform === 'visionos', | 				/* for compat only */ 'global.isAndroid': platform === 'android', | ||||||
| 			process: 'global.process', | 				/* for compat only */ 'global.isIOS': | ||||||
|  | 					platform === 'ios' || platform === 'visionos', | ||||||
| 			// todo: ?!?! | 				/* for compat only */ 'global.isVisionOS': platform === 'visionos', | ||||||
| 			// profile: '() => {}', | 				process: 'global.process', | ||||||
| 		}, | 			}, | ||||||
| 	]); | 		] as any, | ||||||
|  | 	); | ||||||
|  |  | ||||||
| 	// enable DotEnv | 	// enable DotEnv | ||||||
| 	applyDotEnvPlugin(config); | 	applyDotEnvPlugin(config); | ||||||
|  | |||||||
| @ -12,7 +12,10 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { | |||||||
| 	const entryPath = getEntryPath(); | 	const entryPath = getEntryPath(); | ||||||
| 	const virtualEntryPath = path.resolve( | 	const virtualEntryPath = path.resolve( | ||||||
| 		__dirname, | 		__dirname, | ||||||
| 		'../stubs/virtual-entry-typescript.js' | 		// Note: this is possible if needed | ||||||
|  | 		// at moment it's not but just leaving as note for futre | ||||||
|  | 		// `../stubs/virtual-entry-typescript.${env.commonjs ? 'js' : 'mjs'}`, | ||||||
|  | 		`../stubs/virtual-entry-typescript.js`, | ||||||
| 	); | 	); | ||||||
|  |  | ||||||
| 	// exclude files starting with _ from require.context | 	// exclude files starting with _ from require.context | ||||||
| @ -23,7 +26,7 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { | |||||||
| 	chainedSetAddAfter( | 	chainedSetAddAfter( | ||||||
| 		config.entry('bundle'), | 		config.entry('bundle'), | ||||||
| 		'@nativescript/core/globals/index', | 		'@nativescript/core/globals/index', | ||||||
| 		virtualEntryPath | 		virtualEntryPath, | ||||||
| 	); | 	); | ||||||
|  |  | ||||||
| 	config.when(env.hmr, (config) => { | 	config.when(env.hmr, (config) => { | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import path from 'path'; | import path from 'path'; | ||||||
|  |  | ||||||
|  | import { satisfies } from 'semver'; | ||||||
| import { getPackageJson, getProjectRootPath } from './project'; | import { getPackageJson, getProjectRootPath } from './project'; | ||||||
|  |  | ||||||
| // todo: memoize | // todo: memoize | ||||||
| @ -67,3 +68,77 @@ export function getDependencyVersion(dependencyName: string): string | null { | |||||||
| 	} | 	} | ||||||
| 	return null; | 	return null; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Resolve a usable version string for checks (eg. semver.satisfies). | ||||||
|  |  * Strategy: | ||||||
|  |  *  - prefer installed package.json version (getDependencyVersion) | ||||||
|  |  *  - fall back to declared version in project package.json (dependencies/devDependencies) | ||||||
|  |  *  - if declared is a common dist-tag (alpha|beta|rc|next) return a 9.x prerelease | ||||||
|  |  */ | ||||||
|  | export function getResolvedDependencyVersionForCheck( | ||||||
|  | 	dependencyName: string, | ||||||
|  | 	target: string, | ||||||
|  | ): string | null { | ||||||
|  | 	// try installed | ||||||
|  | 	const installed = getDependencyVersion(dependencyName); | ||||||
|  | 	if (installed) { | ||||||
|  | 		return installed; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// try declared in project package.json | ||||||
|  | 	const pkg = getPackageJson(); | ||||||
|  | 	const declared = | ||||||
|  | 		(pkg.dependencies && pkg.dependencies[dependencyName]) || | ||||||
|  | 		(pkg.devDependencies && pkg.devDependencies[dependencyName]); | ||||||
|  | 	if (!declared) { | ||||||
|  | 		return null; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// if declared already satisfies semver check, use it | ||||||
|  | 	// Note: declared may be a dist-tag like 'alpha' or a range. We only treat | ||||||
|  | 	// common tags as prereleases of target. Avoid trying to interpret arbitrary | ||||||
|  | 	// ranges here. | ||||||
|  |  | ||||||
|  | 	// common dist-tags -> treat as prerelease of 9.x for the purpose of >=9 checks | ||||||
|  | 	if (/^(alpha|beta|rc|next)$/.test(String(declared))) { | ||||||
|  | 		return `${target}-0`; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return declared ?? null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Numeric comparison that treats prerelease versions as being at the same | ||||||
|  |  * numeric level as their base version. e.g. 9.0.0-alpha.2 >= 9.0.0 | ||||||
|  |  */ | ||||||
|  | export function isVersionGteConsideringPrerelease( | ||||||
|  | 	version: string | null | undefined, | ||||||
|  | 	target: string, | ||||||
|  | ): boolean { | ||||||
|  | 	if (!version) { | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	try { | ||||||
|  | 		const v = require('semver').parse(String(version)); | ||||||
|  | 		const t = require('semver').parse(String(target)); | ||||||
|  | 		if (!v || !t) { | ||||||
|  | 			// fallback to semver.satisfies with a prerelease-aware lower bound | ||||||
|  | 			return require('semver').satisfies(String(version), `>=${target}-0`); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (v.major > t.major) return true; | ||||||
|  | 		if (v.major < t.major) return false; | ||||||
|  | 		if (v.minor > t.minor) return true; | ||||||
|  | 		if (v.minor < t.minor) return false; | ||||||
|  | 		if (v.patch >= t.patch) return true; | ||||||
|  | 		return false; | ||||||
|  | 	} catch (e) { | ||||||
|  | 		try { | ||||||
|  | 			return require('semver').satisfies(String(version), `>=${target}-0`); | ||||||
|  | 		} catch (e) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | |||||||
| @ -50,6 +50,9 @@ export interface IWebpackEnv { | |||||||
| 	// print webpack stats (default: true) | 	// print webpack stats (default: true) | ||||||
| 	stats?: boolean; | 	stats?: boolean; | ||||||
|  |  | ||||||
|  | 	// enable commonjs modules (default: ES modules, esm) | ||||||
|  | 	commonjs?: boolean; | ||||||
|  |  | ||||||
| 	// misc | 	// misc | ||||||
| 	replace?: string[] | string; | 	replace?: string[] | string; | ||||||
| 	watchNodeModules?: boolean; | 	watchNodeModules?: boolean; | ||||||
| @ -158,10 +161,9 @@ export function chainWebpack( | |||||||
|  * @param mergeFn An object or a function that optionally returns an object (can mutate the object directly and return nothing) |  * @param mergeFn An object or a function that optionally returns an object (can mutate the object directly and return nothing) | ||||||
|  */ |  */ | ||||||
| export function mergeWebpack( | export function mergeWebpack( | ||||||
| 	mergeFn: (( | 	mergeFn: | ||||||
| 		config: Partial<webpack.Configuration>, | 		| ((config: Partial<webpack.Configuration>, env: IWebpackEnv) => any) | ||||||
| 		env: IWebpackEnv, | 		| Partial<webpack.Configuration>, | ||||||
| 	) => any) | Partial<webpack.Configuration>, |  | ||||||
| ) { | ) { | ||||||
| 	webpackMerges.push(mergeFn); | 	webpackMerges.push(mergeFn); | ||||||
| } | } | ||||||
|  | |||||||
| @ -24,15 +24,10 @@ export default function loader(content: string, map: any) { | |||||||
| 		return this.callback(null, `${content}\n${hmrRuntime}`, map); | 		return this.callback(null, `${content}\n${hmrRuntime}`, map); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const relativePath = relative( |  | ||||||
| 		opts.appPath ?? this.rootContext, |  | ||||||
| 		this.resourcePath |  | ||||||
| 	).replace(/\\/g, '/'); |  | ||||||
|  |  | ||||||
| 	const hmrCode = this.hot | 	const hmrCode = this.hot | ||||||
| 		? dedent` | 		? dedent` | ||||||
| 			/* NATIVESCRIPT-HOT-LOADER */ | 			/* NATIVESCRIPT-HOT-LOADER */ | ||||||
| 			if(module.hot && global._isModuleLoadedForUI && global._isModuleLoadedForUI("./${relativePath}")) { | 			if(module.hot?.accept) { | ||||||
| 				module.hot.accept() | 				module.hot.accept() | ||||||
| 			} | 			} | ||||||
| 		` | 		` | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								packages/webpack5/src/plugins/CompatDefinePlugin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/webpack5/src/plugins/CompatDefinePlugin.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | export class CompatDefinePlugin { | ||||||
|  | 	private readonly definitions: Record<string, any>; | ||||||
|  |  | ||||||
|  | 	constructor(definitions: Record<string, any>) { | ||||||
|  | 		this.definitions = definitions || {}; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	apply(compiler: any) { | ||||||
|  | 		// Use the same webpack instance as the compiler to avoid version mismatches | ||||||
|  | 		const wp = compiler?.webpack || require('webpack'); | ||||||
|  | 		const DefinePlugin = wp.DefinePlugin || require('webpack').DefinePlugin; | ||||||
|  | 		new DefinePlugin(this.definitions).apply(compiler); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -37,22 +37,28 @@ export class PlatformSuffixPlugin { | |||||||
| 		// require.context | 		// require.context | ||||||
| 		compiler.hooks.contextModuleFactory.tap(id, (cmf) => { | 		compiler.hooks.contextModuleFactory.tap(id, (cmf) => { | ||||||
| 			// @ts-ignore | 			// @ts-ignore | ||||||
| 			cmf.hooks.alternativeRequests.tap(id, (modules, options) => { | 			const altHook = (cmf as any).hooks?.alternativeRequests; | ||||||
| 				const additionalModules = []; | 			if (altHook && typeof altHook.tap === 'function') { | ||||||
| 				// we are looking for modules that are platform specific (something.<platform>.ext) | 				altHook.tap(id, (modules: any[], options: any) => { | ||||||
| 				// and we are duplicating them without the platform suffix | 					const additionalModules = []; | ||||||
| 				// this allows using require.context with non-existent platformless filenames | 					// we are looking for modules that are platform specific (something.<platform>.ext) | ||||||
| 				// but mapped to the platform specific variant (done in the resolver hook below) | 					// and we are duplicating them without the platform suffix | ||||||
| 				for (const module of modules) { | 					// this allows using require.context with non-existent platformless filenames | ||||||
| 					if (platformRE.test(module.request)) { | 					// but mapped to the platform specific variant (done in the resolver hook below) | ||||||
| 						additionalModules.push({ | 					for (const module of modules) { | ||||||
| 							...module, | 						if (platformRE.test(module.request)) { | ||||||
| 							request: module.request.replace(platformRE, '.'), | 							additionalModules.push({ | ||||||
| 						}); | 								...module, | ||||||
|  | 								request: module.request.replace(platformRE, '.'), | ||||||
|  | 							}); | ||||||
|  | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 					modules.push(...additionalModules); | ||||||
| 				modules.push(...additionalModules); | 				}); | ||||||
| 			}); | 			} else { | ||||||
|  | 				// Hook may be absent on some webpack versions; skip gracefully | ||||||
|  | 				// console.log(`[${id}] alternativeRequests hook not available; skipping.`) | ||||||
|  | 			} | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		compiler.resolverFactory.hooks.resolver | 		compiler.resolverFactory.hooks.resolver | ||||||
| @ -73,57 +79,68 @@ export class PlatformSuffixPlugin { | |||||||
| 				// 	}); | 				// 	}); | ||||||
| 				// }) | 				// }) | ||||||
|  |  | ||||||
| 				resolver.hooks.normalResolve.tapAsync( | 				const normalResolveHook = (resolver as any).hooks?.normalResolve; | ||||||
| 					id, | 				const ensureHook = (name: string) => { | ||||||
| 					(request_, resolveContext, callback) => { | 					return typeof (resolver as any).ensureHook === 'function' | ||||||
| 						for (const platform of this.extensions) { | 						? (resolver as any).ensureHook(name) | ||||||
| 							const { path, request } = request_; | 						: (resolver as any).hooks?.[name]; | ||||||
| 							const ext = request && extname(request); | 				}; | ||||||
| 							const platformExt = ext ? `.${platform}${ext}` : ''; |  | ||||||
|  |  | ||||||
| 							if (path && request && ext && !request.includes(platformExt)) { | 				if ( | ||||||
| 								const platformRequest = request.replace(ext, platformExt); | 					!normalResolveHook || | ||||||
| 								const extPath = resolve(path, platformRequest); | 					typeof normalResolveHook.tapAsync !== 'function' | ||||||
|  | 				) { | ||||||
|  | 					// Missing or incompatible hook; skip to avoid crashes | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 								// console.log({ | 				normalResolveHook.tapAsync(id, (request_, resolveContext, callback) => { | ||||||
| 								// 	path, | 					for (const platform of this.extensions) { | ||||||
| 								// 	request, | 						const { path, request } = request_; | ||||||
| 								// 	ext, | 						const ext = request && extname(request); | ||||||
| 								// 	extPath | 						const platformExt = ext ? `.${platform}${ext}` : ''; | ||||||
| 								// }) |  | ||||||
|  |  | ||||||
| 								// if a file with the same + a platform suffix exists | 						if (path && request && ext && !request.includes(platformExt)) { | ||||||
| 								// we want to resolve that file instead | 							const platformRequest = request.replace(ext, platformExt); | ||||||
| 								if (existsSync(extPath)) { | 							const extPath = resolve(path, platformRequest); | ||||||
| 									const message = `resolving "${request}" to "${platformRequest}"`; |  | ||||||
| 									const hook = resolver.ensureHook('normalResolve'); |  | ||||||
| 									console.log(message); |  | ||||||
|  |  | ||||||
| 									// here we are creating a new resolve object and replacing the path | 							// console.log({ | ||||||
| 									// with the .<platform>.<ext> suffix | 							// 	path, | ||||||
| 									const obj = { | 							// 	request, | ||||||
| 										...request_, | 							// 	ext, | ||||||
| 										path: resolver.join(path, platformRequest), | 							// 	extPath | ||||||
| 										relativePath: | 							// }) | ||||||
| 											request_.relativePath && |  | ||||||
| 											resolver.join(request_.relativePath, platformRequest), |  | ||||||
| 										request: undefined, |  | ||||||
| 									}; |  | ||||||
|  |  | ||||||
| 									// we call to the actual resolver to do the resolving of this new file | 							// if a file with the same + a platform suffix exists | ||||||
| 									return resolver.doResolve( | 							// we want to resolve that file instead | ||||||
| 										hook, | 							if (existsSync(extPath)) { | ||||||
| 										obj, | 								const message = `resolving "${request}" to "${platformRequest}"`; | ||||||
| 										message, | 								const hook = ensureHook('normalResolve'); | ||||||
| 										resolveContext, |  | ||||||
| 										callback | 								// here we are creating a new resolve object and replacing the path | ||||||
| 									); | 								// with the .<platform>.<ext> suffix | ||||||
| 								} | 								const obj = { | ||||||
|  | 									...request_, | ||||||
|  | 									path: resolver.join(path, platformRequest), | ||||||
|  | 									relativePath: | ||||||
|  | 										request_.relativePath && | ||||||
|  | 										resolver.join(request_.relativePath, platformRequest), | ||||||
|  | 									request: undefined, | ||||||
|  | 								}; | ||||||
|  |  | ||||||
|  | 								// we call to the actual resolver to do the resolving of this new file | ||||||
|  | 								return (resolver as any).doResolve( | ||||||
|  | 									hook as any, | ||||||
|  | 									obj, | ||||||
|  | 									message, | ||||||
|  | 									resolveContext, | ||||||
|  | 									callback, | ||||||
|  | 								); | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 						callback(); |  | ||||||
| 					} | 					} | ||||||
| 				); | 					callback(); | ||||||
|  | 				}); | ||||||
| 				// 	resolver.hooks.rawFile.tap(id, (request, resolveContext, callback) => { | 				// 	resolver.hooks.rawFile.tap(id, (request, resolveContext, callback) => { | ||||||
| 				// 		if(request.path && !/\.ios\..+$/.test(request.path)) { | 				// 		if(request.path && !/\.ios\..+$/.test(request.path)) { | ||||||
| 				// 			const { ext } = parse(request.path) | 				// 			const { ext } = parse(request.path) | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								packages/webpack5/src/polyfills/mdn-data-at-rules.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/webpack5/src/polyfills/mdn-data-at-rules.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | /** | ||||||
|  |  * Mock for mdn-data/css/at-rules.json | ||||||
|  |  * Returns empty object since css-tree has its own comprehensive data | ||||||
|  |  * This prevents css-tree from failing when trying to patch its data | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | // Return empty object - css-tree will use its built-in data instead | ||||||
|  | export = {}; | ||||||
							
								
								
									
										8
									
								
								packages/webpack5/src/polyfills/mdn-data-properties.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/webpack5/src/polyfills/mdn-data-properties.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | /** | ||||||
|  |  * Mock for mdn-data/css/properties.json | ||||||
|  |  * Returns empty object since css-tree has its own comprehensive data | ||||||
|  |  * This prevents css-tree from failing when trying to patch its data | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | // Return empty object - css-tree will use its built-in data instead | ||||||
|  | export = {}; | ||||||
							
								
								
									
										8
									
								
								packages/webpack5/src/polyfills/mdn-data-syntaxes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/webpack5/src/polyfills/mdn-data-syntaxes.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | /** | ||||||
|  |  * Mock for mdn-data/css/syntaxes.json | ||||||
|  |  * Returns empty object since css-tree has its own comprehensive data | ||||||
|  |  * This prevents css-tree from failing when trying to patch its data | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | // Return empty object - css-tree will use its built-in data instead | ||||||
|  | export = {}; | ||||||
							
								
								
									
										47
									
								
								packages/webpack5/src/polyfills/module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								packages/webpack5/src/polyfills/module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | |||||||
|  | /** | ||||||
|  |  * Polyfill for Node.js 'module' built-in | ||||||
|  |  * Provides minimal implementation for NativeScript environment | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | // Mock createRequire function that css-tree uses | ||||||
|  | function createRequire(filename: string) { | ||||||
|  | 	// Return a mock require function | ||||||
|  | 	return function mockRequire(id: string) { | ||||||
|  | 		// Handle css-tree's internal patch.json file | ||||||
|  | 		if (id.includes('../data/patch.json') || id.includes('patch.json')) { | ||||||
|  | 			// Return css-tree's patch structure | ||||||
|  | 			return { | ||||||
|  | 				atrules: {}, | ||||||
|  | 				properties: {}, | ||||||
|  | 				types: {}, | ||||||
|  | 			}; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// For mdn-data files, return empty objects | ||||||
|  | 		if (id.includes('mdn-data')) { | ||||||
|  | 			return {}; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// For any other requires, return empty object | ||||||
|  | 		return {}; | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CommonJS export | ||||||
|  | module.exports = { | ||||||
|  | 	createRequire: createRequire, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Provide a named export for ESM consumers: `import { createRequire } from 'module'` | ||||||
|  | try { | ||||||
|  | 	Object.defineProperty(module.exports, 'createRequire', { | ||||||
|  | 		enumerable: true, | ||||||
|  | 		value: createRequire, | ||||||
|  | 	}); | ||||||
|  | 	// Also export under an __esModule flag for certain bundlers | ||||||
|  | 	Object.defineProperty(module.exports, '__esModule', { | ||||||
|  | 		value: true, | ||||||
|  | 	}); | ||||||
|  | } catch (e) { | ||||||
|  | 	// ignore in environments where defineProperty is unavailable | ||||||
|  | } | ||||||
| @ -1,5 +1,5 @@ | |||||||
| // VIRTUAL ENTRY START | // VIRTUAL ENTRY START | ||||||
| require('@nativescript/core/bundle-entry-points') | require('@nativescript/core/bundle-entry-points') | ||||||
| const context = require.context("~/", /* deep: */ true, /* filter: */ /.(xml|js|s?css)$/); | const context = require.context("~/", /* deep: */ true, /* filter: */ /.(xml|js|s?css)$/); | ||||||
| global.registerWebpackModules(context); | global.registerBundlerModules(context); | ||||||
| // VIRTUAL ENTRY END | // VIRTUAL ENTRY END | ||||||
| @ -1,5 +1,5 @@ | |||||||
| // VIRTUAL ENTRY START | // VIRTUAL ENTRY START | ||||||
| require('@nativescript/core/bundle-entry-points') | require('@nativescript/core/bundle-entry-points') | ||||||
| const context = require.context("~/", /* deep: */ true, /* filter: */ /\.(xml|js|(?<!\.d\.)ts|s?css)$/); | const context = require.context("~/", /* deep: */ true, /* filter: */ /\.(xml|js|(?<!\.d\.)ts|s?css)$/); | ||||||
| global.registerWebpackModules(context); | global.registerBundlerModules(context); | ||||||
| // VIRTUAL ENTRY END | // VIRTUAL ENTRY END | ||||||
							
								
								
									
										68
									
								
								packages/webpack5/src/stubs/virtual-entry-typescript.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								packages/webpack5/src/stubs/virtual-entry-typescript.mjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | |||||||
|  | // VIRTUAL ENTRY START | ||||||
|  | require('@nativescript/core/bundle-entry-points') | ||||||
|  |  | ||||||
|  | import { readdirSync } from 'fs'; | ||||||
|  | import { join, extname, relative } from 'path'; | ||||||
|  | import { fileURLToPath } from 'url'; | ||||||
|  | import { createRequire } from 'module'; | ||||||
|  |  | ||||||
|  | if (typeof import.meta.glob !== 'undefined') { | ||||||
|  | 	// Vite environment | ||||||
|  | 	const modules = import.meta.glob( | ||||||
|  | 		// adjust the pattern to your layout: | ||||||
|  | 		'~/**/*.@(xml|js|ts|scss|css)', | ||||||
|  | 		{ eager: true }, // uncomment to import immediately | ||||||
|  | 	); | ||||||
|  | 	global.registerBundlerModules(modules); | ||||||
|  | } else { | ||||||
|  | 	const require = createRequire(import.meta.url); | ||||||
|  | 	const root = fileURLToPath(new URL('./src', import.meta.url)); | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Recursively walk `dir`, collecting all files whose extension is in `exts`, | ||||||
|  | 	 * ignoring any `*.d.ts` files. | ||||||
|  | 	 */ | ||||||
|  | 	function collectFilesSync( | ||||||
|  | 		dir, | ||||||
|  | 		exts = ['.xml', '.js', '.ts', '.css', '.scss'], | ||||||
|  | 	) { | ||||||
|  | 		let results = []; | ||||||
|  | 		for (const entry of readdirSync(dir, { withFileTypes: true })) { | ||||||
|  | 			const full = join(dir, entry.name); | ||||||
|  |  | ||||||
|  | 			if (entry.isDirectory()) { | ||||||
|  | 				results = results.concat(collectFilesSync(full, exts)); | ||||||
|  | 			} else { | ||||||
|  | 				// skip declaration files | ||||||
|  | 				if (entry.name.endsWith('.d.ts')) continue; | ||||||
|  | 				// filter by extension | ||||||
|  | 				if (exts.includes(extname(entry.name))) { | ||||||
|  | 					results.push(full); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return results; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Synchronously load every matching module under `./src`, | ||||||
|  | 	 * keyed by its path relative to `root`. | ||||||
|  | 	 */ | ||||||
|  | 	function loadContextSync() { | ||||||
|  | 		const files = collectFilesSync(root); | ||||||
|  | 		const context = {}; | ||||||
|  |  | ||||||
|  | 		for (const fullPath of files) { | ||||||
|  | 			// make the key look like fast‑glob’s relative paths | ||||||
|  | 			const relPath = relative(root, fullPath).replace(/\\/g, '/'); | ||||||
|  | 			// require() each module | ||||||
|  | 			context[relPath] = require(fullPath); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return context; | ||||||
|  | 	} | ||||||
|  | 	const context = loadContextSync(); | ||||||
|  |  | ||||||
|  | 	global.registerBundlerModules(context); | ||||||
|  | } | ||||||
|  | // VIRTUAL ENTRY END | ||||||
		Reference in New Issue
	
	Block a user
	 Nathan Walker
					Nathan Walker