HTML Entry source code analysis

brief introduction

From the birth reason of HTML Entry - > principle description - > practical application - > source code analysis, I will take you to analyze the HTML Entry framework in an all-round way.

preface

The word HTML Entry may be unfamiliar to you. After all, what is HTML Entry on google? Can't find the right results. But if you know the micro front end, you may know something.

To readers

Based on the principle of not wasting everyone's time, it is hereby explained that if you can understand what HTML Entry is?? Part, you can continue to read. If you don't understand, it is recommended to read the recommended materials before you come back

What's wrong with JS Entry

When it comes to HTML Entry, we have to mention another word JS Entry, because HTML Entry is to solve the problems faced by JS Entry.

The two most famous frameworks in the field of micro front-end are single spa and qiankun. The latter makes secondary packaging based on the former and solves some problems of the former.

Single spa does two things:

  • Loading micro applications (the loading method has to be implemented by the user)
  • Manage the status of micro applications (initialization, mount, uninstall)

The concept of JS Entry is used when loading micro applications. When using single spa to load micro applications, we load not the micro application itself, but the JS file exported by the micro application, and an object will be exported in the entry file. There are three life cycle methods that must be provided by bootstrap, Mount and unmount to access the single spa framework, The mount method specifies how the micro application should be attached to the container node provided by the main application. Of course, if you want to access a micro application, you need to make a series of modifications to the micro application. However, the problem of JS Entry is here. During the transformation, the intrusion of the micro application is too strong, and the coupling with the main application is too strong.

Single spa uses JS Entry to access micro applications. Micro application transformation is generally divided into three steps:

  • Micro application routing transformation, adding a specific prefix
  • Transformation of micro application portal, change of mount point and export of life cycle function
  • Packaging tool configuration changes

Intrusive strong actually means the third point. To change the configuration of the packaging tool and use single spa to access the micro application, you need to package the whole micro application into a JS file and publish it to the static resource server, then configure the address of the JS file in the main application and tell single spa to load the micro application at this address.

Not to mention anything else, there is a big problem with this change. Packaging the whole micro application into a JS file basically eliminates the common packaging optimization, such as on-demand loading, first screen resource loading optimization, css independent packaging and other optimization measures.

Note: sub applications can also make multiple packages, and then use the webpack manifest plugin of webpack to package the manifest JSON file to generate a resource list, and then the loadApp of the main application remotely reads the list file of each sub application and loads the resources in the file in turn; However, the scheme can not enjoy the on-demand loading capacity of sub applications

There is a bug after the project is released, and it needs to be updated online after repair. In order to clear the application brought by the browser cache, the file name will generally be brought with chunkcontent. After the micro application is released, the file name will change. At this time, it is also necessary to update the micro application configuration in the main application, and then recompile the main application and then release. This set of operations is simply unbearable, which is also true Single spa of micro front-end framework from entry to mastery In this article, the reason why development is selected for the environment configuration of micro application publishing in the sample project.

In order to solve the problem of JS Entry, qiankun framework adopts the way of HTML Entry, which makes it as simple for users to access micro applications as iframe.

If the above content is not understood, it means that this article is not suitable for you to read. It is recommended to read it Single spa of micro front-end framework from entry to mastery , this article describes in detail the basic use and source code principle of single spa. Reading this article after reading it will have twice the result with half the effort. Please don't force readers to read it, otherwise they may get dizzy.

HTML Entry

HTML Entry is implemented by the import HTML Entry library. The first screen content of the specified address, i.e. html page, is loaded through http request, and then the html template is parsed to obtain templates, scripts, entry and styles

{
  template: Processed scripts, link,script The labels are annotated out,
  scripts: [Scripted http Address or { async: true, src: xx } Or code block],
  styles: [Stylized http address],
     entry: The address of the entry script, if not marked with entry of script of src,Or the last one script Tagged src
}

Then remotely load the style content in styles and replace the link tag commented out in the template template with the corresponding style element.

Then expose a Promise object outward

{
  // Template is the template after replacing link with style
    template: embedHTML,
    // Static resource address
    assetPublicPath,
    // Get the external script, and finally get the code content of all scripts
    getExternalScripts: () => getExternalScripts(scripts, fetch),
    // Gets the contents of an external style file
    getExternalStyleSheets: () => getExternalStyleSheets(styles, fetch),
    // Script executor, allowing JS code (scripts) to run in the specified context
    execScripts: (proxy, strictGlobal) => {
        if (!scripts.length) {
            return Promise.resolve();
        }
        return execScripts(entry, scripts, proxy, { fetch, strictGlobal });
    }
}

This is the principle of HTML Entry. For more details, please continue to read the source code analysis section below

practical application

In order to solve the problem of JS Entry, qiankun framework adopts the way of HTML Entry, which makes it as simple for users to access micro applications as iframe.

Through the above reading, we know that HTML Entry will eventually return a Promise object. qiankun uses the three items of template, assetPublicPath and execScripts in this object to add the template to the main application through DOM operation, execute the execScripts method to obtain the life cycle method of micro application export, and also solves the problem of JS global pollution, Because when executing the execScripts method, you can specify the JS execution context through the proxy parameter.

More specific content can be read qiankun of micro front-end framework from introduction to source code analysis

HTML Entry source code analysis

importEntry

/**
 * Load the first screen content of the specified address
 * @param {*} entry It can be an address in string format, such as localhost:8080, or a configuration object, such as {scripts, styles, html}
 * @param {*} opts
 * return importHTML Implementation results of
 */
export function importEntry(entry, opts = {}) {
    // Parse the fetch method and getTemplate method from the opt parameter. If not, use the default method
    const { fetch = defaultFetch, getTemplate = defaultGetTemplate } = opts;
    // A method of obtaining static resource address
    const getPublicPath = opts.getPublicPath || opts.getDomain || defaultGetPublicPath;

    if (!entry) {
        throw new SyntaxError('entry should not be empty!');
    }

    // html entry, which is an address in string format
    if (typeof entry === 'string') {
        return importHTML(entry, { fetch, getPublicPath, getTemplate });
    }

    // config entry, entry is an object = {scripts, styles, html}
    if (Array.isArray(entry.scripts) || Array.isArray(entry.styles)) {

        const { scripts = [], styles = [], html = '' } = entry;
        const setStylePlaceholder2HTML = tpl => styles.reduceRight((html, styleSrc) => `${genLinkReplaceSymbol(styleSrc)}${html}`, tpl);
        const setScriptPlaceholder2HTML = tpl => scripts.reduce((html, scriptSrc) => `${html}${genScriptReplaceSymbol(scriptSrc)}`, tpl);

        return getEmbedHTML(getTemplate(setScriptPlaceholder2HTML(setStylePlaceholder2HTML(html))), styles, { fetch }).then(embedHTML => ({
            template: embedHTML,
            assetPublicPath: getPublicPath(entry),
            getExternalScripts: () => getExternalScripts(scripts, fetch),
            getExternalStyleSheets: () => getExternalStyleSheets(styles, fetch),
            execScripts: (proxy, strictGlobal) => {
                if (!scripts.length) {
                    return Promise.resolve();
                }
                return execScripts(scripts[scripts.length - 1], scripts, proxy, { fetch, strictGlobal });
            },
        }));

    } else {
        throw new SyntaxError('entry scripts or styles should be array!');
    }
}

importHTML

/**
 * Load the first screen content of the specified address
 * @param {*} url 
 * @param {*} opts 
 * return Promise<{
      // template Is the template after replacing link with style
        template: embedHTML,
        // Static resource address
        assetPublicPath,
        // Get the external script, and finally get the code content of all scripts
        getExternalScripts: () => getExternalScripts(scripts, fetch),
        // Gets the contents of an external style file
        getExternalStyleSheets: () => getExternalStyleSheets(styles, fetch),
        // Script executor, allowing JS code (scripts) to run in the specified context
        execScripts: (proxy, strictGlobal) => {
            if (!scripts.length) {
                return Promise.resolve();
            }
            return execScripts(entry, scripts, proxy, { fetch, strictGlobal });
        },
   }>
 */
export default function importHTML(url, opts = {}) {
    // Three default methods
    let fetch = defaultFetch;
    let getPublicPath = defaultGetPublicPath;
    let getTemplate = defaultGetTemplate;

    if (typeof opts === 'function') {
        // if branch, compatible with the legacy importHTML api, ops can be a fetch method directly
        fetch = opts;
    } else {
        // Override the default method with user passed parameters, if provided
        fetch = opts.fetch || defaultFetch;
        getPublicPath = opts.getPublicPath || opts.getDomain || defaultGetPublicPath;
        getTemplate = opts.getTemplate || defaultGetTemplate;
    }

    // Request the url through the fetch method, which is why qiankun requires your micro application to support cross domain
    return embedHTMLCache[url] || (embedHTMLCache[url] = fetch(url)
        // response.text() is an html template
        .then(response => response.text())
        .then(html => {

            // Get static resource address
            const assetPublicPath = getPublicPath(url);
            /**
          * Resolve the address of the external script or the address of the code block and link tag of the inline script from the html template
             * {
              *     template: The processed script, link and script tags are commented out,
       *     scripts: [http address of the script or {async: true, src: xx} or code block],
       *  styles: [http address of style],
          *     entry: The address of the entry script is either the src of the script marked entry or the src of the last script tag
              * }
             */
            const { template, scripts, entry, styles } = processTpl(getTemplate(html), assetPublicPath);

            // The getEmbedHTML method remotely loads all external styles through fetch, and then replaces the corresponding link annotation tag with style, that is, the external style is replaced with the inline style, and then returns embedHTML, that is, the processed HTML template
            return getEmbedHTML(template, styles, { fetch }).then(embedHTML => ({
                // Template is the template after replacing link with style
                template: embedHTML,
                // Static resource address
                assetPublicPath,
                // Get the external script, and finally get the code content of all scripts
                getExternalScripts: () => getExternalScripts(scripts, fetch),
                // Gets the contents of an external style file
                getExternalStyleSheets: () => getExternalStyleSheets(styles, fetch),
                // Script executor, allowing JS code (scripts) to run in the specified context
                execScripts: (proxy, strictGlobal) => {
                    if (!scripts.length) {
                        return Promise.resolve();
                    }
                    return execScripts(entry, scripts, proxy, { fetch, strictGlobal });
                },
            }));
        }));
}

processTpl

/**
 * Resolve the address of the external script or the address of the code block and link tag of the inline script from the html template
 * @param tpl html Template
 * @param baseURI
 * @stripStyles whether to strip the css links
 * @returns {{template: void | string | *, scripts: *[], entry: *}}
 * return {
 *     template: The processed script, link and script tags are commented out,
 *     scripts: [http address of the script or {async: true, src: xx} or code block],
 *  styles: [http address of style],
 *     entry: The address of the entry script is either the src of the script marked entry or the src of the last script tag
 * }
 */
export default function processTpl(tpl, baseURI) {

    let scripts = [];
    const styles = [];
    let entry = null;
    // Judge whether the browser supports es module, < script type = "module" / >
    const moduleSupport = isModuleScriptSupported();

    const template = tpl

        // Remove the comment content in the html template <-- xx -->
        .replace(HTML_COMMENT_REGEX, '')

        // Match link label
        .replace(LINK_TAG_REGEX, match => {
            /**
             * Turn the link tag in the template into a comment. If there is a link with href attribute and not preloaded, save the address into the styles array. If it is preloaded, the link will directly become a comment
             */
            // <link rel = "stylesheet" />
            const styleType = !!match.match(STYLE_TYPE_REGEX);
            if (styleType) {

                // <link rel = "stylesheet" href = "xxx" />
                const styleHref = match.match(STYLE_HREF_REGEX);
                // <link rel = "stylesheet" ignore />
                const styleIgnore = match.match(LINK_IGNORE_REGEX);

                if (styleHref) {

                    // Get href attribute value
                    const href = styleHref && styleHref[2];
                    let newHref = href;

                    // If the href does not have a protocol description and gives a relative address, splice the baseURI to get the complete address
                    if (href && !hasProtocol(href)) {
                        newHref = getEntirePath(href, baseURI);
                    }
                    // Change < link rel = "stylesheet" ignore / > to <-- ignore asset ${url} replaced by import-html-entry -->
                    if (styleIgnore) {
                        return genIgnoreAssetReplaceSymbol(newHref);
                    }

                    // Store the href attribute value in the styles array
                    styles.push(newHref);
                    // < link rel = "stylesheet" href = "XXX" / > becomes <-- link ${linkHref} replaced by import-html-entry -->
                    return genLinkReplaceSymbol(newHref);
                }
            }

            // Match < link rel = "preload or prefetch" href = "XXX" / >, indicating that the resource is preloaded
            const preloadOrPrefetchType = match.match(LINK_PRELOAD_OR_PREFETCH_REGEX) && match.match(LINK_HREF_REGEX) && !match.match(LINK_AS_FONT);
            if (preloadOrPrefetchType) {
                // Get href address
                const [, , linkHref] = match.match(LINK_HREF_REGEX);
                // Change the label to <-- prefetch/preload link ${linkHref} replaced by import-html-entry -->
                return genLinkReplaceSymbol(linkHref, true);
            }

            return match;
        })
        // Match < style > < / style >
        .replace(STYLE_TAG_REGEX, match => {
            if (STYLE_IGNORE_REGEX.test(match)) {
                // < style ignore > < / style > becomes <-- ignore asset style file replaced by import-html-entry -->
                return genIgnoreAssetReplaceSymbol('style file');
            }
            return match;
        })
        // Match < script > < / script >
        .replace(ALL_SCRIPT_REGEX, (match, scriptTag) => {
            // Match < script ignore > < / script >
            const scriptIgnore = scriptTag.match(SCRIPT_IGNORE_REGEX);
            // Matching < script nomodule > < / script > or < script type = "module" > < / script >, all belong to scripts that should be ignored
            const moduleScriptIgnore =
                (moduleSupport && !!scriptTag.match(SCRIPT_NO_MODULE_REGEX)) ||
                (!moduleSupport && !!scriptTag.match(SCRIPT_MODULE_REGEX));
            // in order to keep the exec order of all javascripts

            // <script type = "xx" />
            const matchedScriptTypeMatch = scriptTag.match(SCRIPT_TYPE_REGEX);
            // Get the value of type attribute
            const matchedScriptType = matchedScriptTypeMatch && matchedScriptTypeMatch[2];
            // Verify whether the type is valid. If the type is empty or 'text/javascript', 'module', 'application/javascript', 'text/ecmascript', 'application/ecmascript', it will be regarded as valid
            if (!isValidJavaScriptType(matchedScriptType)) {
                return match;
            }

            // If it is an external script, matching non < script type = "text / ng template" SRC = "XXX" > < / script >
            if (SCRIPT_TAG_REGEX.test(match) && scriptTag.match(SCRIPT_SRC_REGEX)) {
                /*
                collect scripts and replace the ref
                */

                // <script entry />
                const matchedScriptEntry = scriptTag.match(SCRIPT_ENTRY_REGEX);
                // <script src = "xx" />
                const matchedScriptSrcMatch = scriptTag.match(SCRIPT_SRC_REGEX);
                // Script address
                let matchedScriptSrc = matchedScriptSrcMatch && matchedScriptSrcMatch[2];

                if (entry && matchedScriptEntry) {
                    // It indicates that there are two entry addresses, namely two < script entry SRC = "XX" / >
                    throw new SyntaxError('You should not set multiply entry script!');
                } else {
                    // Complete the script address. If there is no protocol, it indicates that it is a relative path. Add baseURI
                    if (matchedScriptSrc && !hasProtocol(matchedScriptSrc)) {
                        matchedScriptSrc = getEntirePath(matchedScriptSrc, baseURI);
                    }

                    // Script entry address
                    entry = entry || matchedScriptEntry && matchedScriptSrc;
                }

                if (scriptIgnore) {
                    // < script ignore > < / script > replace with <-- ignore asset ${url || 'file'} replaced by import-html-entry -->
                    return genIgnoreAssetReplaceSymbol(matchedScriptSrc || 'js file');
                }

                if (moduleScriptIgnore) {
                    // < script nomodule > < / script > or < script type = "module" > < / script > replace with
                    // <!--  Nomodule script ${scriptsrc} ignored by import HTML entry -- > or
                    // <!-- module script ${scriptSrc} ignored by import-html-entry -->
                    return genModuleScriptReplaceSymbol(matchedScriptSrc || 'js file', moduleSupport);
                }

                if (matchedScriptSrc) {
                    // Match < script SRC = 'XX' async / >, indicating that the script is loaded asynchronously
                    const asyncScript = !!scriptTag.match(SCRIPT_ASYNC_REGEX);
                    // Store the script address into the scripts array. If it is loaded asynchronously, store an object {async: true, src: xx}
                    scripts.push(asyncScript ? { async: true, src: matchedScriptSrc } : matchedScriptSrc);
                    // < script SRC = "XX" async / > or < script SRC = "XX" / > replace with 
                    // <!--  Async script ${scriptsrc} replaced by import HTML entry -- > or 
                    // <!-- script ${scriptSrc} replaced by import-html-entry -->
                    return genScriptReplaceSymbol(matchedScriptSrc, asyncScript);
                }

                return match;
            } else {
                // Description is an internal script, < script > XX < / script >
                if (scriptIgnore) {
                    // < script ignore / > replace with <-- ignore asset js file replaced by import-html-entry -->
                    return genIgnoreAssetReplaceSymbol('js file');
                }

                if (moduleScriptIgnore) {
                    // < script nomodule > < / script > or < script type = "module" > < / script > replace with
                    // <!--  Nomodule script ${scriptsrc} ignored by import HTML entry -- > or 
                    // <!-- module script ${scriptSrc} ignored by import-html-entry -->
                    return genModuleScriptReplaceSymbol('js file', moduleSupport);
                }

                // if it is an inline script, < script > XX < / script >, get the code between tags = > XX
                const code = getInlineCode(match);

                // remove script blocks when all of these lines are comments.  Determine whether the code block is all comments
                const isPureCommentBlock = code.split(/[\r\n]+/).every(line => !line.trim() || line.trim().startsWith('//'));

                if (!isPureCommentBlock) {
                    // If it is not a comment, the code block is stored in the scripts array
                    scripts.push(match);
                }

                // < script > XX < / script > replace with <-- inline scripts replaced by import-html-entry -->
                return inlineScriptReplaceSymbol;
            }
        });

    // filter empty script
    scripts = scripts.filter(function (script) {
        return !!script;
    });

    return {
        template,
        scripts,
        styles,
        // set the last script as entry if have not set
        entry: entry || scripts[scripts.length - 1],
    };
}

getEmbedHTML

/**
 * convert external css link to inline style for performance optimization,Convert external style to inline style
 * @param template,html Template
 * @param styles link Style link
 * @param opts = { fetch }
 * @return embedHTML Processed html template
 */
function getEmbedHTML(template, styles, opts = {}) {
    const { fetch = defaultFetch } = opts;
    let embedHTML = template;

    return getExternalStyleSheets(styles, fetch)
        .then(styleSheets => {
            // Through the loop, replace the previously set link comment label with the style label, that is, < style > / * href address * / XX < / style >
            embedHTML = styles.reduce((html, styleSrc, i) => {
                html = html.replace(genLinkReplaceSymbol(styleSrc), `<style>/* ${styleSrc} */${styleSheets[i]}</style>`);
                return html;
            }, embedHTML);
            return embedHTML;
        });
}

getExternalScripts

/**
 * Load the script and finally return the content of the script. Promise < array >, each element is a piece of JS code
 * @param {*} scripts = [Script http address or script content of inline script or {async: true, SRC: XX}]
 * @param {*} fetch 
 * @param {*} errorCallback 
 */
export function getExternalScripts(scripts, fetch = defaultFetch, errorCallback = () => {
}) {

    // Define a method that can load the script of remote specified url. Of course, there is also a cache in it. If the cache is hit, it can be obtained directly from the cache
    const fetchScript = scriptUrl => scriptCache[scriptUrl] ||
        (scriptCache[scriptUrl] = fetch(scriptUrl).then(response => {
            // usually browser treats 4xx and 5xx response of script loading as an error and will fire a script error event
            // https://stackoverflow.com/questions/5625420/what-http-headers-responses-trigger-the-onerror-handler-on-a-script-tag/5625603
            if (response.status >= 400) {
                errorCallback();
                throw new Error(`${scriptUrl} load failed with status ${response.status}`);
            }

            return response.text();
        }));

    return Promise.all(scripts.map(script => {

            if (typeof script === 'string') {
                // String, if not the link address, if not the script content (code)
                if (isInlineCode(script)) {
                    // if it is inline script
                    return getInlineCode(script);
                } else {
                    // external script, load script
                    return fetchScript(script);
                }
            } else {
                // use idle time to load async script
                // Asynchronous script, loaded by requestIdleCallback method
                const { src, async } = script;
                if (async) {
                    return {
                        src,
                        async: true,
                        content: new Promise((resolve, reject) => requestIdleCallback(() => fetchScript(src).then(resolve, reject))),
                    };
                }

                return fetchScript(src);
            }
        },
    ));
}

getExternalStyleSheets

/**
 * Load the style file at the specified address through the fetch method
 * @param {*} styles = [ href ]
 * @param {*} fetch 
 * return Promise<Array>,Each element is a pile of style content
 */
export function getExternalStyleSheets(styles, fetch = defaultFetch) {
    return Promise.all(styles.map(styleLink => {
            if (isInlineCode(styleLink)) {
                // if it is inline style
                return getInlineCode(styleLink);
            } else {
                // external styles, load styles and cache
                return styleCache[styleLink] ||
                    (styleCache[styleLink] = fetch(styleLink).then(response => response.text()));
            }

        },
    ));
}

execScripts

/**
 * FIXME to consistent with browser behavior, we should only provide callback way to invoke success and error event
 * The script executor allows the specified scripts to execute in the specified context
 * @param entry Entry address
 * @param scripts = [Script http address or script content of inline script or {async: true, SRC: XX}] 
 * @param proxy This parameter is passed to the script execution context, global object, and windowProxy generated by qiankun JS sandbox
 * @param opts
 * @returns {Promise<unknown>}
 */
export function execScripts(entry, scripts, proxy = window, opts = {}) {
    const {
        fetch = defaultFetch, strictGlobal = false, success, error = () => {
        }, beforeExec = () => {
        },
    } = opts;

    // Get the contents of all specified external scripts, set the execution context of each script, and then run it through the eval function
    return getExternalScripts(scripts, fetch, error)
        .then(scriptsText => {
            // scriptsText is an array of script contents = > each element is a piece of JS code
            const geval = (code) => {
                beforeExec();
                (0, eval)(code);
            };

            /**
             * 
             * @param {*} scriptSrc Script address
             * @param {*} inlineScript Script content
             * @param {*} resolve 
             */
            function exec(scriptSrc, inlineScript, resolve) {

                // Performance measurement
                const markName = `Evaluating script ${scriptSrc}`;
                const measureName = `Evaluating Time Consuming: ${scriptSrc}`;

                if (process.env.NODE_ENV === 'development' && supportsUserTiming) {
                    performance.mark(markName);
                }

                if (scriptSrc === entry) {
                    // entrance
                    noteGlobalProps(strictGlobal ? proxy : window);

                    try {
                        // bind window.proxy to change `this` reference in script
                        geval(getExecutableScript(scriptSrc, inlineScript, proxy, strictGlobal));
                        const exports = proxy[getGlobalProp(strictGlobal ? proxy : window)] || {};
                        resolve(exports);
                    } catch (e) {
                        // entry error must be thrown to make the promise settled
                        console.error(`[import-html-entry]: error occurs while executing entry script ${scriptSrc}`);
                        throw e;
                    }
                } else {
                    if (typeof inlineScript === 'string') {
                        try {
                            // bind window.proxy to change `this` reference in script is to set the execution context of JS code, and then run the code through eval function
                            geval(getExecutableScript(scriptSrc, inlineScript, proxy, strictGlobal));
                        } catch (e) {
                            // consistent with browser behavior, any independent script evaluation error should not block the others
                            throwNonBlockingError(e, `[import-html-entry]: error occurs while executing normal script ${scriptSrc}`);
                        }
                    } else {
                        // external script marked with async: code loaded asynchronously and run after downloading
                        inlineScript.async && inlineScript?.content
                            .then(downloadedScriptText => geval(getExecutableScript(inlineScript.src, downloadedScriptText, proxy, strictGlobal)))
                            .catch(e => {
                                throwNonBlockingError(e, `[import-html-entry]: error occurs while executing async script ${inlineScript.src}`);
                            });
                    }
                }

                // Performance measurement
                if (process.env.NODE_ENV === 'development' && supportsUserTiming) {
                    performance.measure(measureName, markName);
                    performance.clearMarks(markName);
                    performance.clearMeasures(measureName);
                }
            }

            /**
             * recursion
             * @param {*} i Indicates the script number
             * @param {*} resolvePromise Successful callback 
             */
            function schedule(i, resolvePromise) {

                if (i < scripts.length) {
                    // Address of the ith script
                    const scriptSrc = scripts[i];
                    // Contents of the ith script
                    const inlineScript = scriptsText[i];

                    exec(scriptSrc, inlineScript, resolvePromise);
                    if (!entry && i === scripts.length - 1) {
                        // resolve the promise while the last script executed and entry not provided
                        resolvePromise();
                    } else {
                        // Call the next script recursively
                        schedule(i + 1, resolvePromise);
                    }
                }
            }

            // Start scheduling from script 0
            return new Promise(resolve => schedule(0, success || resolve));
        });
}

epilogue

The above is the whole content of HTML Entry. It is also an indispensable part of in-depth understanding of micro front end, single spa and qiankun. The source code is in github

Read here. If you want to further understand the micro front end, single spa, qiankun, etc., you are recommended to read the following contents

Thank you for your likes, collections and comments. I'll see you next time.

When learning becomes habit, knowledge becomes commonsense. Scanning code concerns WeChat official account, learning and progress together. The article has been included in github , welcome to Watch and Star.

Keywords: Javascript Front-end ECMAScript

Added by ram4nd on Sat, 05 Feb 2022 13:06:47 +0200