diff --git a/package-lock.json b/package-lock.json
index 4a9c2c58..da9fc9b3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11255,6 +11255,79 @@
"integrity": "sha512-808EqPQbmUD6/IMpWUXLOZcblCHf9xaiB+un0RYNNE9+6VRjoiw6Be8R32tZ0ips1PX/15tlnA2Ev4UUgg827Q==",
"dev": true
},
+ "ts-loader": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.4.0.tgz",
+ "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.1.0",
+ "enhanced-resolve": "^4.0.0",
+ "loader-utils": "^2.0.0",
+ "micromatch": "^4.0.0",
+ "semver": "^7.3.4"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "semver": {
+ "version": "7.3.7",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
+ "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
@@ -11319,6 +11392,12 @@
"is-typedarray": "^1.0.0"
}
},
+ "typescript": {
+ "version": "4.7.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
+ "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
+ "dev": true
+ },
"unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@@ -11362,7 +11441,7 @@
"unicode-trie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz",
- "integrity": "sha512-WgVuO0M2jDl7hVfbPgXv2LUrD81HM0bQj/bvLGiw6fJ4Zo8nNFnDrA0/hU2Te/wz6pjxCm5cxJwtLjo2eyV51Q==",
+ "integrity": "sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU=",
"dev": true,
"requires": {
"pako": "^0.2.5",
diff --git a/package.json b/package.json
index 24d6bf5a..51cc64d5 100644
--- a/package.json
+++ b/package.json
@@ -60,11 +60,14 @@
"style-loader": "^2.0.0",
"svelte": "^3.44.3",
"svelte-loader": "^3.1.2",
+ "ts-loader": "^8.4.0",
+ "typescript": "^4.7.4",
"url-loader": "^4.1.1",
"webpack": "^4.46.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^3.11.3",
+ "webpack-merge": "^5.8.0",
"worker-loader": "^2.0.0"
}
}
diff --git a/src/packager/plist.js b/src/packager/plist.js
deleted file mode 100644
index 3f03f589..00000000
--- a/src/packager/plist.js
+++ /dev/null
@@ -1,122 +0,0 @@
-// Parses and generates Apple Info.plist files
-// Example file:
-/*
-
-
-
-
- BuildMachineOSBuild
- 20F71
- CFBundleDevelopmentRegion
- en
- CFBundleExecutable
- WebView
- CFBundleIconFile
- AppIcon
- CFBundleIconName
- AppIcon
- CFBundleIdentifier
- org.turbowarp.webviews.mac
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- WebView
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- 1.0
- CFBundleSupportedPlatforms
-
- MacOSX
-
- CFBundleVersion
- 1
- DTCompiler
- com.apple.compilers.llvm.clang.1_0
- DTPlatformBuild
- 12E507
- DTPlatformName
- macosx
- DTPlatformVersion
- 11.3
- DTSDKBuild
- 20E214
- DTSDKName
- macosx11.3
- DTXcode
- 1251
- DTXcodeBuild
- 12E507
- LSApplicationCategoryType
- public.app-category.games
- LSMinimumSystemVersion
- 10.12
- NSMainStoryboardFile
- Main
- NSPrincipalClass
- NSApplication
-
-
-*/
-
-const xmlToValue = (node) => {
- if (node.tagName === 'dict') {
- const result = {};
- for (const child of node.children) {
- if (child.tagName === 'key') {
- result[child.textContent] = xmlToValue(child.nextElementSibling);
- }
- }
- return result;
- } else if (node.tagName === 'array') {
- return Array.from(node.children).map(xmlToValue);
- } else if (node.tagName === 'string') {
- return node.textContent;
- }
- console.warn('unknown plist xml', node);
- return null;
-};
-
-const valueToXml = (doc, value) => {
- if (Array.isArray(value)) {
- const node = doc.createElement('array');
- for (const listItem of value) {
- node.appendChild(valueToXml(doc, listItem));
- }
- return node;
- } else if (typeof value === 'object') {
- const node = doc.createElement('dict');
- for (const [key, keyValue] of Object.entries(value)) {
- const keyNode = doc.createElement('key');
- keyNode.textContent = key;
- const valueNode = valueToXml(doc, keyValue);
- node.appendChild(keyNode);
- node.appendChild(valueNode);
- }
- return node;
- } else if (typeof value === 'string') {
- const node = doc.createElement('string');
- node.textContent = value;
- return node;
- }
- console.warn('unknown plist value', value);
- return valueToXml(doc, `${value}`);
-};
-
-export const parsePlist = (string) => {
- const xml = new DOMParser().parseFromString(string, 'text/xml');
- const rootNode = xml.children[0];
- const rootDict = rootNode.children[0];
- return xmlToValue(rootDict);
-};
-
-export const generatePlist = (values) => {
- const xml = document.implementation.createDocument(null, "plist");
- const rootNode = xml.documentElement;
- rootNode.setAttribute('version', '1.0');
- rootNode.appendChild(valueToXml(xml, values));
- const serialized = new XMLSerializer().serializeToString(xml);
- return `
-
-${serialized}`;
-};
diff --git a/src/packager/plist.ts b/src/packager/plist.ts
new file mode 100644
index 00000000..1fa7ad5f
--- /dev/null
+++ b/src/packager/plist.ts
@@ -0,0 +1,134 @@
+// Parses and generates Apple Info.plist files
+// Example file:
+/*
+
+
+
+
+ BuildMachineOSBuild
+ 20F71
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ WebView
+ CFBundleIconFile
+ AppIcon
+ CFBundleIconName
+ AppIcon
+ CFBundleIdentifier
+ org.turbowarp.webviews.mac
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ WebView
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSupportedPlatforms
+
+ MacOSX
+
+ CFBundleVersion
+ 1
+ DTCompiler
+ com.apple.compilers.llvm.clang.1_0
+ DTPlatformBuild
+ 12E507
+ DTPlatformName
+ macosx
+ DTPlatformVersion
+ 11.3
+ DTSDKBuild
+ 20E214
+ DTSDKName
+ macosx11.3
+ DTXcode
+ 1251
+ DTXcodeBuild
+ 12E507
+ LSApplicationCategoryType
+ public.app-category.games
+ LSMinimumSystemVersion
+ 10.12
+ NSMainStoryboardFile
+ Main
+ NSPrincipalClass
+ NSApplication
+
+
+*/
+
+type PlistValue = string | Plist | PlistValue[];
+interface Plist {
+ [s: string]: PlistValue;
+}
+
+const xmlToValue = (node: Element): PlistValue => {
+ if (node.tagName === 'dict') {
+ const result: Plist = {};
+ for (const child of node.children) {
+ if (child.tagName === 'key') {
+ const next = child.nextElementSibling;
+ if (!next) {
+ throw new Error('Plist dict key is missing value');
+ }
+ result[child.textContent!] = xmlToValue(next);
+ }
+ }
+ return result;
+ } else if (node.tagName === 'array') {
+ return Array.from(node.children).map(xmlToValue);
+ } else if (node.tagName === 'string') {
+ return node.textContent!;
+ }
+ throw new Error('Failed to parse plist value');
+};
+
+const valueToXml = (doc: XMLDocument, value: PlistValue): Element => {
+ if (Array.isArray(value)) {
+ const node = doc.createElement('array');
+ for (const listItem of value) {
+ node.appendChild(valueToXml(doc, listItem));
+ }
+ return node;
+ } else if (typeof value === 'object') {
+ const node = doc.createElement('dict');
+ for (const [key, keyValue] of Object.entries(value)) {
+ const keyNode = doc.createElement('key');
+ keyNode.textContent = key;
+ const valueNode = valueToXml(doc, keyValue);
+ node.appendChild(keyNode);
+ node.appendChild(valueNode);
+ }
+ return node;
+ } else if (typeof value === 'string') {
+ const node = doc.createElement('string');
+ node.textContent = value;
+ return node;
+ }
+ console.warn('unknown plist value', value);
+ return valueToXml(doc, `${value}`);
+};
+
+export const parsePlist = (string: string): Plist => {
+ const xml = new DOMParser().parseFromString(string, 'text/xml');
+ const rootNode = xml.children[0];
+ const rootDict = rootNode.children[0];
+ const plist = xmlToValue(rootDict);
+ if (typeof plist !== 'object' || Array.isArray(plist)) {
+ throw new Error('Top level object in plist was not a dict');
+ }
+ return plist;
+};
+
+export const generatePlist = (values: Plist): string => {
+ const xml = document.implementation.createDocument(null, "plist");
+ const rootNode = xml.documentElement;
+ rootNode.setAttribute('version', '1.0');
+ rootNode.appendChild(valueToXml(xml, values));
+ const serialized = new XMLSerializer().serializeToString(xml);
+ return `
+
+${serialized}`;
+};
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..4d5ed12d
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "outDir": "./dist/",
+ "target": "es2018",
+ "module": "es6",
+ "moduleResolution": "node",
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "allowJs": true,
+ "strict": true,
+ "noImplicitAny": true
+ }
+}
diff --git a/webpack.config.js b/webpack.config.js
index 90f3f52b..8fb057d1 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,15 +3,13 @@ const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const CopyWebpackPlugin = require('copy-webpack-plugin');
+const merge = require('webpack-merge').merge;
const AddBuildIDToOutputPlugin = require('./src/build/add-build-id-to-output-plugin');
const GenerateServiceWorkerPlugin = require('./src/build/generate-service-worker-plugin');
const EagerDynamicImportPlugin = require('./src/build/eager-dynamic-import-plugin');
const isProduction = process.env.NODE_ENV === 'production';
const isStandalone = !!process.env.STANDALONE;
-const base = {
- mode: isProduction ? 'production' : 'development'
-};
const dist = path.resolve(__dirname, 'dist');
const buildId = isProduction ? require('./src/build/generate-scaffolding-build-id') : null;
@@ -30,8 +28,32 @@ const getVersion = () => {
};
const version = getVersion();
-const makeScaffolding = ({full}) => ({
- ...base,
+const globalBase = {
+ mode: isProduction ? 'production' : 'development',
+ resolve: {
+ extensions: ['.ts', '.js']
+ },
+ module: {
+ rules: [
+ {
+ test: /\.tsx?$/,
+ use: 'ts-loader',
+ exclude: /node_modules/
+ }
+ ]
+ }
+};
+
+const frontendBase = {
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env.SCAFFOLDING_BUILD_ID': buildId ? JSON.stringify(buildId) : 'Math.random().toString()',
+ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
+ })
+ ]
+};
+
+const makeScaffolding = ({full}) => merge(globalBase, frontendBase, {
devtool: isProduction ? '' : 'source-map',
output: {
filename: 'scaffolding/[name].js',
@@ -121,15 +143,7 @@ const makeScaffolding = ({full}) => ({
]
});
-const commonFrontendPlugins = () => [
- new webpack.DefinePlugin({
- 'process.env.SCAFFOLDING_BUILD_ID': buildId ? JSON.stringify(buildId) : 'Math.random().toString()',
- 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
- })
-];
-
-const makeWebsite = () => ({
- ...base,
+const makeWebsite = () => merge(globalBase, frontendBase, {
devtool: isStandalone ? '' : 'source-map',
output: {
filename: isProduction ? 'js/[name].[contenthash].js' : 'js/[name].js',
@@ -142,7 +156,7 @@ const makeWebsite = () => ({
alias: {
svelte: path.resolve('node_modules', 'svelte')
},
- extensions: ['.mjs', '.js', '.svelte'],
+ extensions: ['.svelte'],
mainFields: ['svelte', 'browser', 'module', 'main']
},
optimization: {
@@ -171,7 +185,6 @@ const makeWebsite = () => ({
]
},
plugins: [
- ...commonFrontendPlugins(),
new CopyWebpackPlugin({
patterns: [
{
@@ -205,8 +218,7 @@ const makeWebsite = () => ({
},
});
-const makeNode = () => ({
- ...base,
+const makeNode = () => merge(globalBase, {
devtool: '',
target: 'node',
output: {
@@ -236,7 +248,6 @@ const makeNode = () => ({
]
},
plugins: [
- ...commonFrontendPlugins(),
...(process.env.BUNDLE_ANALYZER === 'node' ? [new BundleAnalyzerPlugin()] : [])
],
});