Skip to content

Commit 1767b15

Browse files
added lowcoder-sdk-webpack-bundle workspace
1 parent d2ca35e commit 1767b15

17 files changed

+6883
-63
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"presets": [
3+
"@babel/preset-typescript",
4+
"@babel/preset-react",
5+
["@babel/preset-env", {
6+
"modules": false,
7+
"targets": {
8+
"node": "6.10"
9+
}
10+
}]
11+
],
12+
"plugins": [
13+
["@babel/transform-runtime"],
14+
["babel-plugin-styled-components"],
15+
["import", {
16+
"libraryName": "antd",
17+
"libraryDirectory": "es",
18+
"style": true
19+
}, "antd"],
20+
["import", {
21+
"libraryName": "@ant-design/icons",
22+
// "style": false,
23+
"libraryDirectory": "es/icons",
24+
"camel2DashComponentName": false
25+
}, "@ant-design/icons"],
26+
["import", {
27+
"libraryName": "antd-mobile"
28+
}, "antd-mobile"],
29+
["import", {
30+
"libraryName": "lodash"
31+
}, "lodash"]
32+
]
33+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/node_modules
2+
/dist
3+
/bundle
4+
/types
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# lowcoder-sdk-webpack-bundle
2+
The code to use Lowcoder Apps on any Web Project
3+
4+
STEPS to use
5+
6+
1) build the project with `npm run build`
7+
2) host `dist` folder on server
8+
3) add `bundle.js` as an external script on the HTML page where you want to include the app
9+
4) Add the apps as given in demo under `/dist/index_test.html`
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
7+
<title>YOUR APP TITLE</title>
8+
<script>
9+
//Output change Handler. MAKE SURE you use same method name
10+
opChange = (op) => {
11+
console.log("Output changed", op);
12+
};
13+
14+
//Module Event Handler. MAKE SURE you use same method name
15+
onModuleEventTriggered = (eventName) => {
16+
console.log(eventName);
17+
};
18+
</script>
19+
</head>
20+
<body>
21+
<!--* App 1 Start *-->
22+
<h1>App 1</h1>
23+
<div class="lowcoder-module-container">
24+
<input type="hidden" class="module-id" value="6533c298b3f4097d93c215d8" />
25+
<!-- <input type="hidden" class="module-input" value="" /> -->
26+
<input
27+
type="hidden"
28+
class="locoder-backend-url"
29+
value="https://api-service.lowcoder.cloud"
30+
/>
31+
<div class="lowcoder-module-display"></div>
32+
</div>
33+
<!--* App 1 End *-->
34+
<h1>App 2</h1>
35+
<!--* App 2 Start *-->
36+
<div class="lowcoder-module-container">
37+
<input type="hidden" class="module-id" value="644d3a19b5af567870e62c2a" />
38+
<input
39+
type="hidden"
40+
class="module-input"
41+
value='{"moduleInput1":"test"}'
42+
/>
43+
<input
44+
type="hidden"
45+
class="locoder-backend-url"
46+
value="https://api-service.lowcoder.cloud"
47+
/>
48+
<div class="lowcoder-module-display"></div>
49+
</div>
50+
<!--* App 2 End ** -->
51+
52+
<!--* Required Bundle Script ** -->
53+
<script src="./bundle.js"></script>
54+
</body>
55+
</html>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom";
3+
import { LowcoderAppView } from "./src/index";
4+
5+
const opChangeDefault = (op) => {};
6+
const onModuleEventTriggeredDefault = (op) => {};
7+
8+
for (
9+
var i = 0;
10+
i < document.querySelectorAll(".lowcoder-module-container").length;
11+
i++
12+
) {
13+
const App = () => {
14+
return (
15+
<LowcoderAppView
16+
appId={
17+
document
18+
.querySelectorAll(".lowcoder-module-container")
19+
[i]?.querySelector(".module-id")?.value
20+
}
21+
onModuleOutputChange={
22+
typeof opChange !== "undefined" ? opChange : opChangeDefault
23+
}
24+
onModuleEventTriggered={
25+
typeof onModuleEventTriggered !== "undefined"
26+
? onModuleEventTriggered
27+
: onModuleEventTriggeredDefault
28+
}
29+
moduleInputs={JSON.parse(
30+
document
31+
.querySelectorAll(".lowcoder-module-container")
32+
[i]?.querySelector(".module-input")?.value || "{}"
33+
)}
34+
baseUrl={
35+
document
36+
.querySelectorAll(".lowcoder-module-container")
37+
[i]?.querySelector(".locoder-backend-url")?.value ||
38+
"https://api-service.lowcoder.cloud"
39+
}
40+
/>
41+
);
42+
}
43+
44+
ReactDOM.render(
45+
React.createElement(App, {}, null),
46+
document
47+
.querySelectorAll(".lowcoder-module-container")
48+
[i]?.querySelector(".lowcoder-module-display")
49+
);
50+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "lowcoder-sdk-webpack-bundle",
3+
"description": "",
4+
"version": "2.1.2",
5+
"main": "index.jsx",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"start": "webpack-dev-server --config ./webpack.config.cjs --mode development --progress --color --port 5000",
9+
"build": "webpack --mode production"
10+
},
11+
"author": "Lowcoder Software LTD",
12+
"license": "AGPL3",
13+
"devDependencies": {
14+
"@svgr/webpack": "^8.1.0",
15+
"babel-loader": "^9.1.3",
16+
"babel-plugin-import": "^1.13.8",
17+
"babel-plugin-styled-components": "^2.1.4",
18+
"copy-webpack-plugin": "^12.0.2",
19+
"css-loader": "^6.10.0",
20+
"file-loader": "^6.2.0",
21+
"less-loader": "^12.2.0",
22+
"raw-loader": "^4.0.2",
23+
"style-loader": "^3.3.4",
24+
"ts-loader": "^9.5.1",
25+
"tsconfig-paths-webpack-plugin": "^4.1.0",
26+
"typescript": "^4.8.4",
27+
"url-loader": "^4.1.1",
28+
"webpack": "^5.90.3",
29+
"webpack-bundle-analyzer": "^4.10.1",
30+
"webpack-cli": "^5.1.4",
31+
"webpack-dev-server": "^5.0.4"
32+
},
33+
"peerDependencies": {
34+
"react": ">=18",
35+
"react-dom": ">=18"
36+
}
37+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
const path = require('path')
2+
3+
class JSEntryWebpackPlugin {
4+
constructor(options = {}) {
5+
this.options = {
6+
filename: 'index.js',
7+
template: 'auto',
8+
publicPath: options.publicPath === undefined ? 'auto' : options.publicPath,
9+
...options,
10+
}
11+
}
12+
13+
apply(compiler) {
14+
compiler.hooks.emit.tap('InjectCssEntry', async compilation => {
15+
// console.log(Object.keys(compilation.assets))
16+
17+
/** output filenames for the given entry names */
18+
const entryNames = Object.keys(compiler.options.entry)
19+
const outputFileNames = new Set(
20+
(entryNames.length ? entryNames : ['main']).map(entryName =>
21+
// Replace '[name]' with entry name
22+
this.options.filename.replace(/\[name\]/g, entryName),
23+
),
24+
)
25+
26+
/** Option for every entry point */
27+
const entryOption = Array.from(outputFileNames).map(filename => ({
28+
...this.options,
29+
filename,
30+
}))[0]
31+
32+
/** The public path used inside the html file */
33+
const publicPath = this.getPublicPath(
34+
compilation,
35+
entryOption.filename,
36+
entryOption.publicPath,
37+
)
38+
39+
/** build output path */
40+
const templatePath = this.getTemplatePath(entryOption.template, compilation.options)
41+
42+
/** Generated file paths from the entry point names */
43+
const assets = this.htmlWebpackPluginAssets(
44+
compilation,
45+
// 只处理一个
46+
Array.from(compilation.entrypoints.keys()).slice(0, 1),
47+
publicPath,
48+
templatePath,
49+
)
50+
51+
// js entry
52+
if (!compilation.assets[assets.entry]) return
53+
// const { libsImportCode } = await import("../src/dev-utils/external.js");
54+
// const libs = libsImportCode(["lowcoder-sdk"]);
55+
// ${libsImportCode(["lowcoder-sdk"])}
56+
let content = `(function() {
57+
58+
// adding entry points to single bundle.js file
59+
let scripts = ${JSON.stringify(assets.js)};
60+
for (let i = 0; i < scripts.length; i++) {
61+
const scriptEle = document.createElement('script');
62+
scriptEle.src = scripts[i];
63+
document.body.appendChild(scriptEle);
64+
}
65+
})()`;
66+
67+
compilation.assets[entryOption.filename] = {
68+
source() {
69+
return content
70+
},
71+
size() {
72+
return content.length
73+
},
74+
}
75+
})
76+
}
77+
78+
htmlWebpackPluginAssets(compilation, entryNames, publicPath, templatePath) {
79+
// https://github.com/jantimon/html-webpack-plugin/blob/main/index.js#L640
80+
const assets = {
81+
publicPath,
82+
templatePath,
83+
entry: '',
84+
js: [],
85+
css: [],
86+
}
87+
88+
// Extract paths to .js, .mjs and .css files from the current compilation
89+
const entryPointPublicPathMap = {}
90+
const extensionRegexp = /\.(css|js)(\?|$)/
91+
for (let i = 0; i < entryNames.length; i++) {
92+
const entryName = entryNames[i]
93+
/** entryPointUnfilteredFiles - also includes hot module update files */
94+
const entryPointUnfilteredFiles = compilation.entrypoints.get(entryName).getFiles()
95+
96+
const entryPointFiles = entryPointUnfilteredFiles.filter(chunkFile => {
97+
// compilation.getAsset was introduced in webpack 4.4.0
98+
// once the support pre webpack 4.4.0 is dropped please
99+
// remove the following guard:
100+
const asset = compilation.getAsset && compilation.getAsset(chunkFile)
101+
if (!asset) {
102+
return true
103+
}
104+
// Prevent hot-module files from being included:
105+
const assetMetaInformation = asset.info || {}
106+
return !(assetMetaInformation.hotModuleReplacement || assetMetaInformation.development)
107+
})
108+
109+
// Prepend the publicPath and append the hash depending on the
110+
// webpack.output.publicPath and hashOptions
111+
// E.g. bundle.js -> /bundle.js?hash
112+
const entryPointPublicPaths = entryPointFiles.map(chunkFile => {
113+
const urlPath = this.urlencodePath(chunkFile)
114+
const entryPointPublicPath = publicPath + urlPath
115+
116+
if (chunkFile.endsWith('.js')) {
117+
assets.entry = urlPath
118+
}
119+
return entryPointPublicPath
120+
})
121+
122+
entryPointPublicPaths.forEach(entryPointPublicPath => {
123+
const extMatch = extensionRegexp.exec(entryPointPublicPath)
124+
// Skip if the public path is not a .css, .mjs or .js file
125+
if (!extMatch) {
126+
return
127+
}
128+
// Skip if this file is already known
129+
// (e.g. because of common chunk optimizations)
130+
if (entryPointPublicPathMap[entryPointPublicPath]) {
131+
return
132+
}
133+
entryPointPublicPathMap[entryPointPublicPath] = true
134+
const ext = extMatch[1]
135+
assets[ext].push(entryPointPublicPath)
136+
})
137+
}
138+
return assets
139+
}
140+
141+
getPublicPath(compilation, outputName, customPublicPath) {
142+
const compilationHash = compilation.hash
143+
144+
/**
145+
* @type {string} the configured public path to the asset root
146+
* if a path publicPath is set in the current webpack config use it otherwise
147+
* fallback to a relative path
148+
*/
149+
const webpackPublicPath = compilation.getAssetPath(compilation.outputOptions.publicPath, {
150+
hash: compilationHash,
151+
})
152+
153+
// Webpack 5 introduced "auto" as default value
154+
const isPublicPathDefined = webpackPublicPath !== 'auto'
155+
156+
let publicPath =
157+
// If the html-webpack-plugin options contain a custom public path uset it
158+
customPublicPath !== 'auto'
159+
? customPublicPath
160+
: isPublicPathDefined
161+
? // If a hard coded public path exists use it
162+
webpackPublicPath
163+
: // If no public path was set get a relative url path
164+
path
165+
.relative(
166+
path.resolve(compilation.options.output.path, path.dirname(outputName)),
167+
compilation.options.output.path,
168+
)
169+
.split(path.sep)
170+
.join('/')
171+
172+
if (publicPath.length && publicPath.substr(-1, 1) !== '/') {
173+
publicPath += '/'
174+
}
175+
176+
return publicPath
177+
}
178+
179+
getTemplatePath(template, options) {
180+
const { context, output } = options
181+
182+
return template === 'auto'
183+
? path.join(output.path, path.sep)
184+
: path.join(context || '', template)
185+
}
186+
187+
urlencodePath(filePath) {
188+
// some+path/demo.html?value=abc?def
189+
const queryStringStart = filePath.indexOf('?')
190+
const urlPath = queryStringStart === -1 ? filePath : filePath.substr(0, queryStringStart)
191+
const queryString = filePath.substr(urlPath.length)
192+
// Encode all parts except '/' which are not part of the querystring:
193+
const encodedUrlPath = urlPath.split('/').map(encodeURIComponent).join('/')
194+
return encodedUrlPath + queryString
195+
}
196+
}
197+
198+
JSEntryWebpackPlugin.version = 1
199+
200+
module.exports = JSEntryWebpackPlugin

0 commit comments

Comments
 (0)