@farmfe/runtime
Version:
Runtime of Farm
228 lines • 9.72 kB
JavaScript
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