Files
docker_dev/lowcoder/client/packages/lowcoder-sdk-webpack-bundle/plugins/JSEntryWebpackPlugin.cjs
2025-11-17 18:45:35 +01:00

201 lines
6.6 KiB
JavaScript

const path = require('path')
class JSEntryWebpackPlugin {
constructor(options = {}) {
this.options = {
filename: 'index.js',
template: 'auto',
publicPath: options.publicPath === undefined ? 'auto' : options.publicPath,
...options,
}
}
apply(compiler) {
compiler.hooks.emit.tap('InjectCssEntry', async compilation => {
// console.log(Object.keys(compilation.assets))
/** output filenames for the given entry names */
const entryNames = Object.keys(compiler.options.entry)
const outputFileNames = new Set(
(entryNames.length ? entryNames : ['main']).map(entryName =>
// Replace '[name]' with entry name
this.options.filename.replace(/\[name\]/g, entryName),
),
)
/** Option for every entry point */
const entryOption = Array.from(outputFileNames).map(filename => ({
...this.options,
filename,
}))[0]
/** The public path used inside the html file */
const publicPath = this.getPublicPath(
compilation,
entryOption.filename,
entryOption.publicPath,
)
/** build output path */
const templatePath = this.getTemplatePath(entryOption.template, compilation.options)
/** Generated file paths from the entry point names */
const assets = this.htmlWebpackPluginAssets(
compilation,
// 只处理一个
Array.from(compilation.entrypoints.keys()).slice(0, 1),
publicPath,
templatePath,
)
// js entry
if (!compilation.assets[assets.entry]) return
// const { libsImportCode } = await import("../src/dev-utils/external.js");
// const libs = libsImportCode(["lowcoder-sdk"]);
// ${libsImportCode(["lowcoder-sdk"])}
let content = `(function() {
// adding entry points to single bundle.js file
let scripts = ${JSON.stringify(assets.js)};
for (let i = 0; i < scripts.length; i++) {
const scriptEle = document.createElement('script');
scriptEle.src = scripts[i];
document.body.appendChild(scriptEle);
}
})()`;
compilation.assets[entryOption.filename] = {
source() {
return content
},
size() {
return content.length
},
}
})
}
htmlWebpackPluginAssets(compilation, entryNames, publicPath, templatePath) {
// https://github.com/jantimon/html-webpack-plugin/blob/main/index.js#L640
const assets = {
publicPath,
templatePath,
entry: '',
js: [],
css: [],
}
// Extract paths to .js, .mjs and .css files from the current compilation
const entryPointPublicPathMap = {}
const extensionRegexp = /\.(css|js)(\?|$)/
for (let i = 0; i < entryNames.length; i++) {
const entryName = entryNames[i]
/** entryPointUnfilteredFiles - also includes hot module update files */
const entryPointUnfilteredFiles = compilation.entrypoints.get(entryName).getFiles()
const entryPointFiles = entryPointUnfilteredFiles.filter(chunkFile => {
// compilation.getAsset was introduced in webpack 4.4.0
// once the support pre webpack 4.4.0 is dropped please
// remove the following guard:
const asset = compilation.getAsset && compilation.getAsset(chunkFile)
if (!asset) {
return true
}
// Prevent hot-module files from being included:
const assetMetaInformation = asset.info || {}
return !(assetMetaInformation.hotModuleReplacement || assetMetaInformation.development)
})
// Prepend the publicPath and append the hash depending on the
// webpack.output.publicPath and hashOptions
// E.g. bundle.js -> /bundle.js?hash
const entryPointPublicPaths = entryPointFiles.map(chunkFile => {
const urlPath = this.urlencodePath(chunkFile)
const entryPointPublicPath = publicPath + urlPath
if (chunkFile.endsWith('.js')) {
assets.entry = urlPath
}
return entryPointPublicPath
})
entryPointPublicPaths.forEach(entryPointPublicPath => {
const extMatch = extensionRegexp.exec(entryPointPublicPath)
// Skip if the public path is not a .css, .mjs or .js file
if (!extMatch) {
return
}
// Skip if this file is already known
// (e.g. because of common chunk optimizations)
if (entryPointPublicPathMap[entryPointPublicPath]) {
return
}
entryPointPublicPathMap[entryPointPublicPath] = true
const ext = extMatch[1]
assets[ext].push(entryPointPublicPath)
})
}
return assets
}
getPublicPath(compilation, outputName, customPublicPath) {
const compilationHash = compilation.hash
/**
* @type {string} the configured public path to the asset root
* if a path publicPath is set in the current webpack config use it otherwise
* fallback to a relative path
*/
const webpackPublicPath = compilation.getAssetPath(compilation.outputOptions.publicPath, {
hash: compilationHash,
})
// Webpack 5 introduced "auto" as default value
const isPublicPathDefined = webpackPublicPath !== 'auto'
let publicPath =
// If the html-webpack-plugin options contain a custom public path uset it
customPublicPath !== 'auto'
? customPublicPath
: isPublicPathDefined
? // If a hard coded public path exists use it
webpackPublicPath
: // If no public path was set get a relative url path
path
.relative(
path.resolve(compilation.options.output.path, path.dirname(outputName)),
compilation.options.output.path,
)
.split(path.sep)
.join('/')
if (publicPath.length && publicPath.substr(-1, 1) !== '/') {
publicPath += '/'
}
return publicPath
}
getTemplatePath(template, options) {
const { context, output } = options
return template === 'auto'
? path.join(output.path, path.sep)
: path.join(context || '', template)
}
urlencodePath(filePath) {
// some+path/demo.html?value=abc?def
const queryStringStart = filePath.indexOf('?')
const urlPath = queryStringStart === -1 ? filePath : filePath.substr(0, queryStringStart)
const queryString = filePath.substr(urlPath.length)
// Encode all parts except '/' which are not part of the querystring:
const encodedUrlPath = urlPath.split('/').map(encodeURIComponent).join('/')
return encodedUrlPath + queryString
}
}
JSEntryWebpackPlugin.version = 1
module.exports = JSEntryWebpackPlugin