UNPKG

@lynx-js/runtime-wrapper-webpack-plugin

Version:

Use runtime wrapper which allow JavaScript to be load by Lynx.

229 lines 8.41 kB
// Copyright 2024 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import { RuntimeGlobals } from '@lynx-js/webpack-runtime-globals'; const defaultInjectVars = [ 'Card', 'setTimeout', 'setInterval', 'clearInterval', 'clearTimeout', 'NativeModules', 'tt', 'console', 'Component', 'ReactLynx', 'nativeAppId', 'Behavior', 'LynxJSBI', 'lynx', // BOM API 'window', 'document', 'frames', 'self', 'location', 'navigator', 'localStorage', 'history', 'Caches', 'screen', 'alert', 'confirm', 'prompt', 'fetch', 'XMLHttpRequest', '__WebSocket__', // We would provide `WebSocket` using `ProvidePlugin` 'webkit', 'Reporter', 'print', 'global', // Lynx API 'requestAnimationFrame', 'cancelAnimationFrame', ]; /** * RuntimeWrapperWebpackPlugin adds runtime wrappers to JavaScript and allow to be loaded by Lynx. * * @public */ class RuntimeWrapperWebpackPlugin { options; constructor(options = {}) { this.options = options; } /** * `defaultOptions` is the default options that the {@link RuntimeWrapperWebpackPlugin} uses. * * @public */ static defaultOptions = Object.freeze({ targetSdkVersion: '3.2', test: /\.js$/, bannerType: () => 'script', injectVars: defaultInjectVars, experimental_isLazyBundle: false, }); /** * The entry point of a webpack plugin. * @param compiler - the webpack compiler */ apply(compiler) { new RuntimeWrapperWebpackPluginImpl(compiler, Object.assign({}, RuntimeWrapperWebpackPlugin.defaultOptions, this.options)); } } export { RuntimeWrapperWebpackPlugin }; const defaultInjectStr = defaultInjectVars.join(','); class RuntimeWrapperWebpackPluginImpl { compiler; options; name = 'RuntimeWrapperWebpackPlugin'; constructor(compiler, options) { this.compiler = compiler; this.options = options; const { targetSdkVersion, test, experimental_isLazyBundle } = options; const { BannerPlugin } = compiler.webpack; const isDev = process.env['NODE_ENV'] === 'development' || compiler.options.mode === 'development'; let injectStr = defaultInjectStr; if (typeof options.injectVars === 'function') { injectStr = options.injectVars(defaultInjectVars).join(','); } const iife = compiler.options.output.iife ?? true; // banner new BannerPlugin({ test: test, raw: true, banner: ({ chunk, filename }) => { const banner = this.#getBannerType(filename) === 'script' ? loadScriptBanner() : loadBundleBanner(); return banner + amdBanner({ // TODO: config injectStr, overrideRuntimePromise: true, moduleId: '[name].js', targetSdkVersion, iife, }) // In standalone lazy bundle mode, the lazy bundle will // also has chunk.id "main", it will be conflict with the // consumer project. // We disable it for standalone lazy bundle since we do not // support HMR for standalone lazy bundle now. + (isDev && !experimental_isLazyBundle ? lynxChunkEntries(JSON.stringify(chunk.id)) : ''); }, }).apply(compiler); // footer new BannerPlugin({ test: test, footer: true, raw: true, banner: ({ filename }) => { const footer = this.#getBannerType(filename) === 'script' ? loadScriptFooter : loadBundleFooter; return amdFooter('[name].js', iife) + footer; }, }).apply(compiler); } #getBannerType(filename) { const bannerType = this.options.bannerType(filename); if (bannerType !== 'bundle' && bannerType !== 'script') { throw new Error(`The return value of the bannerType expects the 'bundle' or 'script', but received '${bannerType}'.`); } return bannerType; } } function lynxChunkEntries(chunkId) { return `// lynx chunks entries if (!${RuntimeGlobals.lynxChunkEntries}) { // Initialize once ${RuntimeGlobals.lynxChunkEntries} = {}; } if (!${RuntimeGlobals.lynxChunkEntries}[${chunkId}]) { ${RuntimeGlobals.lynxChunkEntries}[${chunkId}] = globDynamicComponentEntry; } else { globDynamicComponentEntry = ${RuntimeGlobals.lynxChunkEntries}[${chunkId}]; } `; } // This is Lynx js runtime code. It will wrap user code (e. app-service.js, chunk.js) // with two function wrapper, the outer function provide some utility functions, such as // * `define` objects that the frontend framework implementor can use to provide some global-looking variables // that are actually specific to the module in the inner function wrapper, such as // * The `module` and `exports` are reserved objects that used to export values from the module in cjs, Lynx // use `requireModule`/`requireModuleAsync` & return object to do `require` / `export` staffs rather than them. // * common JS API `setTimeout`, `setInterval`, `clearInterval`, `clearTimeout`, `NativeModules`, `console`, // * `nativeAppId`, `lynx`, `LynxJSBI` // Besides these, runtime code also contains some injected code to provide certain features. // For example, the Promise object is replaced with a call to "getPromise" when `overrideRuntimePromise` is true. const loadScriptBanner = (strictMode = true) => `(function(){ ${strictMode ? '\'use strict\';' : ';'} var g = (new Function('return this;'))(); function __init_card_bundle__(lynxCoreInject) { g.__bundle__holder = undefined; var globDynamicComponentEntry = g.globDynamicComponentEntry || '__Card__'; var tt = lynxCoreInject.tt;`; const loadBundleBanner = (strictMode = true) => `(function(){ ${strictMode ? '\'use strict\';' : ';'} var eval2 = eval; var g = eval2("this"); function initBundle(lynxCoreInject) { var tt = lynxCoreInject.tt; `; const amdBanner = ({ injectStr, moduleId, overrideRuntimePromise, targetSdkVersion, iife, }) => { const iifeWrapper = iife ? '' : ` // This needs to be wrapped in an IIFE because it needs to be isolated against Lynx injected variables. (() => {`; return (` tt.define("${moduleId}", function(require, module, exports, ${injectStr}) { lynx = lynx || {}; lynx.targetSdkVersion=lynx.targetSdkVersion||${JSON.stringify(targetSdkVersion)}; ${overrideRuntimePromise ? `var Promise = lynx.Promise;` : ''} fetch = fetch || lynx.fetch; requestAnimationFrame = requestAnimationFrame || lynx.requestAnimationFrame; cancelAnimationFrame = cancelAnimationFrame || lynx.cancelAnimationFrame; ${iifeWrapper} `); }; const amdFooter = (moduleId, iife) => ` ${iife ? '' : '})();'} }); return tt.require("${moduleId}");`; // footer for app-service.js chunk const loadScriptFooter = ` };` // FYI: bundleModuleMode // The loadScript behavior of app-service.js is determined by bundleModuleMode // bundleModuleMode: 'EvalRequire' (default, unable to get the correct error stack.) // ------------ // lynx_core.js // const jsContent = nativeApp.readScript('app-service.js'); // eval(jsContent); // ------------- // bundleModuleMode: 'ReturnByFunction' (most used since 2 yrs ago, 2020-11) // ------------ // lynx_core.js // const bundleInitReturnObj: BundleInitReturnObj = nativeApp.loadScript(appServiceName); // if (bundleInitReturnObj && bundleInitReturnObj.init) { // bundleInitReturnObj.init({ tt }); // } // ------------- + ` if (g && g.bundleSupportLoadScript){ var res = {init: __init_card_bundle__}; g.__bundle__holder = res; return res; } else { __init_card_bundle__({"tt": tt}); }; })(); `; const loadBundleFooter = `} g.initBundle = initBundle; })()`; //# sourceMappingURL=RuntimeWrapperWebpackPlugin.js.map