UNPKG

@v4fire/client

Version:

V4Fire client core library

353 lines (289 loc) • 8.66 kB
/*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ const config = require('@config/config'), {src, webpack, csp} = config; const fs = require('fs-extra'); const buble = require('buble'), monic = require('monic'); const {getAssetsDecl} = include('src/super/i-static-page/modules/ss-helpers/assets'), {getScriptDecl, getStyleDecl, normalizeAttrs} = include('src/super/i-static-page/modules/ss-helpers/tags'), {loadLibs, loadStyles, loadLinks} = include('src/super/i-static-page/modules/ss-helpers/libs'); const {getVarsDecl} = include('src/super/i-static-page/modules/ss-helpers/base-declarations'), {needInline, addPublicPath} = include('src/super/i-static-page/modules/ss-helpers/helpers'); const canLoadStylesDeferred = !csp.nonce(), needLoadStylesAsJS = webpack.dynamicPublicPath(); const defAttrs = { crossorigin: webpack.publicPath() === '' ? undefined : 'anonymous' }; exports.getPageScriptDepsDecl = getPageScriptDepsDecl; /** * Returns code to load script dependencies of a page. * * The function returns JS code to load the library by using JS. * You need to put this declaration within a script tag or use the "wrap" option. * * @param {Array<string>} dependencies - list of dependencies to load * @param {!Object<string>} assets - map with static page assets * @param {boolean=} [wrap] - if true, the final code is wrapped by a script tag * @returns {string} */ function getPageScriptDepsDecl(dependencies, {assets, wrap} = {}) { if (!dependencies) { return ''; } let decl = ''; for (const dep of dependencies) { const scripts = [ getScriptDeclByName(`${dep}_tpl`, {assets}), getScriptDeclByName(dep, {assets}) ]; // We can't compile styles into static CSS files because // we have to provide a dynamic public path to them via runtime if (needLoadStylesAsJS) { scripts.unshift(getScriptDeclByName(`${dep}_style`, {assets})); } if (dep === 'index') { scripts.reverse(); } decl += `${scripts.join('\n')}\n`; } if (wrap) { decl = getScriptDecl(decl); } return decl; } exports.getPageStyleDepsDecl = getPageStyleDepsDecl; /** * Returns code to load style dependencies of a page. * * The function can return JS code to load the style by using `document.write` or pure CSS to inline. * You may use the "wrap" option to wrap the final code with a tag to load. * * @param {Array<string>} dependencies - list of dependencies to load * @param {!Object<string>} assets - map with static page assets * @param {boolean=} [wrap] - if true, the final code is wrapped by a tag to load * @param {boolean=} [js] - if true, the function will always return JS code to load the dependency * @returns {string} */ function getPageStyleDepsDecl(dependencies, {assets, wrap, js}) { if (!dependencies || needLoadStylesAsJS) { return ''; } let decl = ''; for (const dep of dependencies) { decl += getStyleDeclByName(dep, {assets, js}); decl += '\n'; } if (wrap && (js || !needInline())) { decl = getScriptDecl(decl); } return decl; } exports.getScriptDeclByName = getScriptDeclByName; /** * Returns code to load a script by the specified name. * The names are equal with entry points from "src/entries". * * The function returns JS code to load the library by using JS. * You need to put this declaration within a script tag or use the "wrap" option. * * @param {string} name * @param {!Object<string>} assets - map with static page assets * @param {boolean=} [optional] - if true, the missing of this script won't throw an error * @param {boolean=} [defer=true] - if true, the script is loaded with the "defer" attribute * @param {boolean=} [inline] - if true, the script is placed as a text * @param {boolean=} [wrap] - if true, the final code is wrapped by a script tag * @returns {string} */ function getScriptDeclByName(name, { assets, optional, defer = true, inline, wrap }) { let decl; if (needInline(inline)) { if (assets[name]) { const filePath = src.clientOutput(assets[name].path); if (fs.existsSync(filePath)) { decl = `include('${filePath}');`; } } else { if (!optional) { throw new ReferenceError(`A script by the name "${name}" is not defined`); } return ''; } } else { decl = getScriptDecl({ ...defAttrs, defer, js: true, src: addPublicPath([`PATH['${name}']`]) }); if (optional) { decl = `if ('${name}' in PATH) { ${decl} }`; } } return wrap ? getScriptDecl(decl) : decl; } exports.getStyleDeclByName = getStyleDeclByName; /** * Returns code to load a style by the specified name. * The names are equal with entry points from "src/entries". * * The function can return JS code to load the style by using JS or pure CSS to inline. * You may use the "wrap" option to wrap the final code with a tag to load. * * @param {string} name * @param {!Object<string>} assets - map with static page assets * @param {boolean=} [optional] - if true, the missing of this style won't throw an error * @param {boolean=} [defer] - if true, the style is loaded only after loading of the whole page * @param {boolean=} [inline] - if true, the style is placed as a text * @param {boolean=} [wrap] - if true, the final code is wrapped by a tag to load * @param {boolean=} [js] - if true, the function will always return JS code to load the dependency * @returns {string} */ function getStyleDeclByName(name, { assets, optional, defer = canLoadStylesDeferred, inline, wrap, js }) { const rname = `${name}_style`; if (needLoadStylesAsJS) { return getScriptDeclByName(rname, {assets, optional, defer, inline, wrap}); } let decl; if (needInline(inline)) { if (assets[rname]) { const filePath = src.clientOutput(assets[rname].path); if (fs.existsSync(filePath)) { decl = getStyleDecl({...defAttrs, js}, `include('${filePath}');`); } } else if (!optional) { throw new ReferenceError(`A style by the name "${name}" is not defined`); } } else { decl = getStyleDecl({ ...defAttrs, defer, js: true, rel: 'stylesheet', src: addPublicPath([`PATH['${rname}']`]) }); if (optional) { decl = `if ('${rname}' in PATH) { ${decl} }`; } } if (!decl) { return ''; } return wrap ? getScriptDecl(decl) : decl; } exports.generateInitJS = generateInitJS; /** * Generates js script to initialize the specified page * * @param pageName * * @param deps - map of external libraries to load * @param ownDeps - own dependencies of the page * * @param assets - map with static page assets * @param assetsRequest - should or not do a request for assets.js * * @param rootTag - type of the root tag (div, span, etc.) * @param rootAttrs - attributes for the root tag * * @returns {!Promise<void>} */ async function generateInitJS(pageName, { deps, ownDeps, assets, assetsRequest, rootTag, rootAttrs }) { if (needInline()) { return; } const head = [], body = []; // - block varsDecl head.push(getVarsDecl()); // - block assets head.push(getAssetsDecl({inline: !assetsRequest, js: true})); // - block links head.push(await loadLinks(deps.links, {assets, js: true})); // - block headStyles head.push(await getStyleDeclByName('std', {assets, optional: true, js: true})); // - block headScripts head.push(await loadLibs(deps.headScripts, {assets, js: true})); body.push(` (function () { var el = document.createElement('${rootTag || 'div'}'); ${normalizeAttrs(rootAttrs, true)} el.setAttribute('class', 'i-static-page ${pageName}'); document.body.appendChild(el); })(); `); // - block styles body.push( await loadStyles(deps.styles, {assets, js: true}), getPageStyleDepsDecl(ownDeps, {assets, js: true}) ); // - block scripts body.push( await getScriptDeclByName('std', {assets, optional: true}), await loadLibs(deps.scripts, {assets, js: true}), getScriptDeclByName('index-core', {assets, optional: true}), getScriptDeclByName('vendor', {assets, optional: true}), getPageScriptDepsDecl(ownDeps, {assets}) ); const bodyInitializer = ` function $__RENDER_ROOT() { ${body.join('\n')} } `; const initPath = src.clientOutput(`${webpack.output({name: pageName})}.init.js`), content = head.join('\n') + bodyInitializer; fs.writeFileSync(initPath, content); let {result} = await monic.compile(initPath, { content, saveFiles: false, replacers: [include('build/monic/include')] }); if (/ES[35]$/.test(config.es())) { result = buble.transform(result).code; } fs.writeFileSync(initPath, result); }