Skip to content

[WIP] Reduce lowcoder-sdk initial bundle size #774

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
webpack configurations
  • Loading branch information
raheeliftikhar5 committed Mar 26, 2024
commit 0d34edd51c9cbb359432ea5ee9f97c83347977d4
198 changes: 198 additions & 0 deletions client/packages/lowcoder-sdk/plugins/JSEntryWebpackPlugin.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
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', 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

let content = `(function() {
// const scripts = [];
let scripts = ${JSON.stringify(assets.js)};
for (let i = 0; i < scripts.length; i++) {
const scriptEle = document.createElement('script');
scriptEle.src = scripts[i];
// scripts.push(scriptEle);
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
152 changes: 10 additions & 142 deletions client/packages/lowcoder-sdk/webpack.config.cjs
Original file line number Diff line number Diff line change
@@ -1,79 +1,11 @@
const path = require("path");
const TerserPlugin = require('terser-webpack-plugin');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
// const RemarkHTML = require("remark-html")
const webpack = require("webpack");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin
const { buildVars } = require("./src/dev-utils/buildVars.cjs");

// import path from "path";
// import { fileURLToPath } from 'url';
// import TerserPlugin from "terser-webpack-plugin";
// import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
// // const RemarkHTML = require("remark-html")
// import webpack from "webpack";

// const __filename = fileURLToPath(import.meta.url);
// const __dirname = path.dirname(__filename);
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const JSEntryWebpackPlugin = require('./plugins/JSEntryWebpackPlugin.cjs');

// const buildVars = [
// {
// name: "PUBLIC_URL",
// defaultValue: "/",
// },
// {
// name: "REACT_APP_EDITION",
// defaultValue: "community",
// },
// {
// name: "REACT_APP_LANGUAGES",
// defaultValue: "",
// },
// {
// name: "REACT_APP_COMMIT_ID",
// defaultValue: "00000",
// },
// {
// name: "REACT_APP_API_HOST",
// defaultValue: "",
// },
// {
// name: "LOWCODER_NODE_SERVICE_URL",
// defaultValue: "",
// },
// {
// name: "REACT_APP_ENV",
// defaultValue: "production",
// },
// {
// name: "REACT_APP_BUILD_ID",
// defaultValue: "",
// },
// {
// name: "REACT_APP_LOG_LEVEL",
// defaultValue: "error",
// },
// {
// name: "REACT_APP_IMPORT_MAP",
// defaultValue: "{}",
// },
// {
// name: "REACT_APP_SERVER_IPS",
// defaultValue: "",
// },
// {
// name: "REACT_APP_BUNDLE_BUILTIN_PLUGIN",
// defaultValue: "",
// },
// {
// name: "REACT_APP_BUNDLE_TYPE",
// defaultValue: "app",
// },
// {
// name: "REACT_APP_DISABLE_JS_SANDBOX",
// defaultValue: "",
// },
// ];
const { buildVars } = require("./src/dev-utils/buildVars.cjs");

const define = {};
buildVars.forEach(({ name, defaultValue }) => {
Expand All @@ -83,7 +15,7 @@ buildVars.forEach(({ name, defaultValue }) => {
const apiBaseUrl = "http://localhost:8000";

module.exports = {
stats: 'verbose',
// stats: 'verbose',
mode: 'production',
entry: "./index-bundle.jsx",
externals: {
Expand Down Expand Up @@ -165,12 +97,12 @@ module.exports = {
})]
},
output: {
// path: __dirname + "/dist",
path: path.resolve(__dirname, 'bundle'),
// publicPath: "https://sdk.lowcoder.cloud/",
publicPath: "/",
// filename: "bundle.js",
filename: '[name].bundle.js',
clean: true,
},
plugins: [
new webpack.DefinePlugin({
Expand All @@ -181,7 +113,11 @@ module.exports = {
new webpack.IgnorePlugin({
resourceRegExp: /.test.(ts|tsx)$/,
}),
new BundleAnalyzerPlugin()
new JSEntryWebpackPlugin({
path: path.resolve(__dirname, 'bundle'),
filename: 'bundle.js'
}),
// new BundleAnalyzerPlugin()
],
optimization: {
minimize: true,
Expand All @@ -192,75 +128,7 @@ module.exports = {
splitChunks: {
chunks: 'all',
},
// splitChunks: {
// cacheGroups: {
// vendor: {
// test: /[\\/]node_modules[\\/]/,
// name: 'vendors',
// chunks: 'all',
// },
// },
// },
// splitChunks: {
// chunks: 'all',
// minSize: 10000,
// minRemainingSize: 0,
// minChunks: 1,
// maxAsyncRequests: 30,
// maxInitialRequests: 30,
// enforceSizeThreshold: 50000,
// cacheGroups: {
// default: {
// minChunks: 2,
// priority: -20,
// reuseExistingChunk: true,
// },
// defaultVendors: {
// test: /[\\/]node_modules[\\/]/,
// priority: -10,
// reuseExistingChunk: true,
// // name(module) {
// // // get the name. E.g. node_modules/packageName/not/this/part.js
// // // or node_modules/packageName
// // const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
// // // if (packageName === 'antd') {
// // // return 'antd';
// // // }
// // // if (packageName === 'antd-mobile') {
// // // return 'antd-mobile';
// // // }
// // // if (packageName === 'lodash') {
// // // return 'lodash';
// // // }
// // // if (packageName === 'moment') {
// // // return 'moment';
// // // }
// // // if (packageName === 'dayjs') {
// // // return 'dayjs';
// // // }
// // // npm package names are URL-safe, but some servers don't like @ symbols
// // // return `npm.${packageName.replace('@', '')}`;
// // // return `npm.${packageName.replace('@', '')}`;
// // return `vendor`;
// // },
// },
// },
// },
runtimeChunk: 'single',
// splitChunks: {
// chunks: 'all',
// }
// splitChunks: {
// minSize: 0,
// cacheGroups: {
// // reuseExistingChunk: true,
// vendor: {
// test: /[\\/]node_modules[\\/]/,
// name: 'vendors',
// chunks: 'all'
// }
// }
// }
},
devServer: {
static: {
Expand Down
Loading