UNPKG

@farmfe/runtime

Version:
228 lines 9.72 kB
import { Module } from "./module.js"; import { FarmRuntimePluginContainer } from "./plugin.js"; import { ResourceLoader, __global_this__, isBrowser, targetEnv, } from "./resource-loader.js"; export class ModuleSystem { constructor() { this.dynamicResources = []; this.modules = {}; this.cache = {}; this.publicPaths = []; this.dynamicModuleResourcesMap = {}; this.resourceLoader = new ResourceLoader(this, this.publicPaths); this.pluginContainer = new FarmRuntimePluginContainer([]); this.targetEnv = targetEnv; this.externalModules = {}; this.reRegisterModules = false; } require(moduleId, isCJS = false) { // return the cached exports if cache exists // console.log(`[Farm] require module "${moduleId}" from cache`); if (this.cache[moduleId]) { const shouldSkip = this.pluginContainer.hookBail("readModuleCache", this.cache[moduleId]); // console.log(`[Farm] shouldSkip: ${shouldSkip} ${moduleId}`); if (!shouldSkip) { const cachedModule = this.cache[moduleId]; return cachedModule.initializer || cachedModule.exports; } } const initializer = this.modules[moduleId]; if (!initializer) { if (this.externalModules[moduleId]) { const exports = this.externalModules[moduleId]; // fix `const assert = require('assert');` when assert is external. This leads to `assert is not a function` error caused by default export different from esm and cjs if (isCJS) { return exports.default || exports; } return exports; } // try node require if target Env is node if ((this.targetEnv === "node" || !isBrowser) && nodeRequire) { const externalModule = nodeRequire(moduleId); return externalModule; } this.pluginContainer.hookSerial("moduleNotFound", moduleId); // return a empty module if the module is not registered console.debug(`[Farm] Module "${moduleId}" is not registered`); return {}; // throw new Error(`Module "${moduleId}" is not registered`); } // create a full new module instance and store it in cache to avoid cyclic initializing const module = new Module(moduleId, this.require.bind(this)); module.resource_pot = initializer.__farm_resource_pot__; // call the module created hook this.pluginContainer.hookSerial("moduleCreated", module); this.cache[moduleId] = module; if (!__global_this__.require) { __global_this__.require = this.require.bind(this); } // initialize the new module const result = initializer(module, module.exports, this.require.bind(this), this.farmDynamicRequire.bind(this)); // it's a async module, return the promise if (result && result instanceof Promise) { module.initializer = result.then(() => { // call the module initialized hook this.pluginContainer.hookSerial("moduleInitialized", module); module.initializer = undefined; // return the exports of the module return module.exports; }); return module.initializer; } else { // call the module initialized hook this.pluginContainer.hookSerial("moduleInitialized", module); // return the exports of the module return module.exports; } } farmDynamicRequire(moduleId) { if (this.modules[moduleId]) { const exports = this.require(moduleId); if (exports.__farm_async) { return exports.default; } else { return Promise.resolve(exports); } } return this.loadDynamicResources(moduleId); } loadDynamicResourcesOnly(moduleId, force = false) { const resources = this.dynamicModuleResourcesMap[moduleId].map((index) => this.dynamicResources[index]); if (!resources || resources.length === 0) { throw new Error(`Dynamic imported module "${moduleId}" does not belong to any resource`); } // force reload resources if (force) { this.clearCache(moduleId); } // loading all required resources, and return the exports of the entry module return Promise.all(resources.map((resource) => { if (force) { const resourceLoaded = this.resourceLoader.isResourceLoaded(resource.path); this.resourceLoader.setLoadedResource(resource.path, false); if (resourceLoaded) { return this.resourceLoader.load({ ...resource, // force reload the resource path: `${resource.path}?t=${Date.now()}` }); } } return this.resourceLoader.load(resource); })); } loadDynamicResources(moduleId, force = false) { const resources = this.dynamicModuleResourcesMap[moduleId].map((index) => this.dynamicResources[index]); return this.loadDynamicResourcesOnly(moduleId, force) .then(() => { // Do not require the module if all the resources are not js resources if (resources.every(resource => resource.type !== 0)) { return; } if (!this.modules[moduleId]) { throw new Error(`Dynamic imported module "${moduleId}" is not registered.`); } const result = this.require(moduleId); // if the module is async, return the default export, the default export should be a promise if (result.__farm_async) { return result.default; } else { return result; } }) .catch((err) => { console.error(`[Farm] Error loading dynamic module "${moduleId}"`, err); throw err; }); } register(moduleId, initializer) { // console.log(`[Farm] register module "${moduleId}"`, console.trace()); if (this.modules[moduleId] && !this.reRegisterModules) { // throw new Error( // `Module "${moduleId}" has registered! It should not be registered twice` // ); console.warn(`Module "${moduleId}" has registered! It should not be registered twice`); return; } this.modules[moduleId] = initializer; } update(moduleId, init) { this.modules[moduleId] = init; this.clearCache(moduleId); } delete(moduleId) { if (this.modules[moduleId]) { this.clearCache(moduleId); delete this.modules[moduleId]; return true; } else { return false; } } getModuleUrl(moduleId) { const publicPath = this.publicPaths[0] ?? ""; if (isBrowser) { const url = `${window.location.protocol}//${window.location.host}${publicPath.endsWith("/") ? publicPath.slice(0, -1) : publicPath}/${this.modules[moduleId].__farm_resource_pot__}`; return url; } else { return this.modules[moduleId].__farm_resource_pot__; } } getCache(moduleId) { return this.cache[moduleId]; } clearCache(moduleId) { if (this.cache[moduleId]) { delete this.cache[moduleId]; return true; } else { return false; } } setInitialLoadedResources(resources) { for (const resource of resources) { this.resourceLoader.setLoadedResource(resource); } } // These two methods are used to support dynamic module loading, the dynamic module info is collected by the compiler and injected during compile time // This method can also be called during runtime to add new dynamic modules setDynamicModuleResourcesMap(dynamicResources, dynamicModuleResourcesMap) { this.dynamicResources = dynamicResources; this.dynamicModuleResourcesMap = dynamicModuleResourcesMap; } // The public paths are injected during compile time setPublicPaths(publicPaths) { this.publicPaths = publicPaths; this.resourceLoader.publicPaths = this.publicPaths; } // The plugins are injected during compile time. setPlugins(plugins) { this.pluginContainer.plugins = plugins; } // This method can be called during runtime to add new plugins addPlugin(plugin) { if (this.pluginContainer.plugins.every((p) => p.name !== plugin.name)) { this.pluginContainer.plugins.push(plugin); } } // This method can be called during runtime to remove plugins removePlugin(pluginName) { this.pluginContainer.plugins = this.pluginContainer.plugins.filter((p) => p.name !== pluginName); } // The external modules are injected during compile time. setExternalModules(externalModules) { Object.assign(this.externalModules, externalModules || {}); } // bootstrap should be called after all three methods above are called, and the bootstrap call is also injected during compile time // This method should only be called once bootstrap() { this.pluginContainer.hookSerial("bootstrap", this); } } //# sourceMappingURL=module-system.js.map