2020-12-24 20:32:05 +02:00
|
|
|
/**
|
|
|
|
|
* Run this script to convert the project to TypeScript. This is only guaranteed to work
|
|
|
|
|
* on the unmodified default template; if you have done code changes you are likely need
|
|
|
|
|
* to touch up the generated project manually.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// @ts-check
|
2020-12-26 02:22:37 +02:00
|
|
|
const fs = require("fs");
|
|
|
|
|
const path = require("path");
|
|
|
|
|
const { argv } = require("process");
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
const projectRoot = argv[2] || path.join(__dirname, "..");
|
2020-12-24 20:32:05 +02:00
|
|
|
|
|
|
|
|
function warn(message) {
|
2020-12-26 02:22:37 +02:00
|
|
|
console.warn("Warning: " + message);
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function replaceInFile(fileName, replacements) {
|
2020-12-26 02:22:37 +02:00
|
|
|
if (fs.existsSync(fileName)) {
|
|
|
|
|
let contents = fs.readFileSync(fileName, "utf8");
|
|
|
|
|
let hadUpdates = false;
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
replacements.forEach(([from, to]) => {
|
|
|
|
|
const newContents = contents.replace(from, to);
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
const isAlreadyApplied = typeof to !== "string" || contents.includes(to);
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
if (newContents !== contents) {
|
|
|
|
|
contents = newContents;
|
|
|
|
|
hadUpdates = true;
|
|
|
|
|
} else if (!isAlreadyApplied) {
|
|
|
|
|
warn(`Wanted to update "${from}" in ${fileName}, but did not find it.`);
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
if (hadUpdates) {
|
|
|
|
|
fs.writeFileSync(fileName, contents);
|
|
|
|
|
} else {
|
|
|
|
|
console.log(`${fileName} had already been updated.`);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
warn(`Wanted to update ${fileName} but the file did not exist.`);
|
|
|
|
|
}
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createFile(fileName, contents) {
|
2020-12-26 02:22:37 +02:00
|
|
|
if (fs.existsSync(fileName)) {
|
|
|
|
|
warn(
|
|
|
|
|
`Wanted to create ${fileName}, but it already existed. Leaving existing file.`
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
fs.writeFileSync(fileName, contents);
|
|
|
|
|
}
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addDepsToPackageJson() {
|
2020-12-26 02:22:37 +02:00
|
|
|
const pkgJSONPath = path.join(projectRoot, "package.json");
|
|
|
|
|
const packageJSON = JSON.parse(fs.readFileSync(pkgJSONPath, "utf8"));
|
|
|
|
|
packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
|
|
|
|
|
"@rollup/plugin-typescript": "^6.0.0",
|
|
|
|
|
"@tsconfig/svelte": "^1.0.10",
|
|
|
|
|
"@types/compression": "^1.7.0",
|
|
|
|
|
"@types/node": "^14.11.1",
|
|
|
|
|
"@types/polka": "^0.5.1",
|
|
|
|
|
"svelte-check": "^1.0.46",
|
|
|
|
|
"svelte-preprocess": "^4.3.0",
|
|
|
|
|
tslib: "^2.0.1",
|
|
|
|
|
typescript: "^4.0.3"
|
|
|
|
|
});
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
// Add script for checking
|
|
|
|
|
packageJSON.scripts = Object.assign(packageJSON.scripts, {
|
|
|
|
|
validate: "svelte-check --ignore src/node_modules/@sapper"
|
|
|
|
|
});
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
// Write the package JSON
|
|
|
|
|
fs.writeFileSync(pkgJSONPath, JSON.stringify(packageJSON, null, " "));
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function changeJsExtensionToTs(dir) {
|
2020-12-26 02:22:37 +02:00
|
|
|
const elements = fs.readdirSync(dir, { withFileTypes: true });
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
for (let i = 0; i < elements.length; i++) {
|
|
|
|
|
if (elements[i].isDirectory()) {
|
|
|
|
|
changeJsExtensionToTs(path.join(dir, elements[i].name));
|
|
|
|
|
} else if (elements[i].name.match(/^((?!json).)*js$/)) {
|
|
|
|
|
fs.renameSync(
|
|
|
|
|
path.join(dir, elements[i].name),
|
|
|
|
|
path.join(dir, elements[i].name).replace(".js", ".ts")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateSingleSvelteFile({ view, vars, contextModule }) {
|
2020-12-26 02:22:37 +02:00
|
|
|
replaceInFile(path.join(projectRoot, "src", `${view}.svelte`), [
|
|
|
|
|
[
|
|
|
|
|
/(?:<script)(( .*?)*?)>/gm,
|
|
|
|
|
(m, attrs) =>
|
|
|
|
|
`<script${attrs}${!attrs.includes('lang="ts"') ? ' lang="ts"' : ""}>`
|
|
|
|
|
],
|
|
|
|
|
...(vars
|
|
|
|
|
? vars.map(({ name, type }) => [
|
|
|
|
|
`export let ${name};`,
|
|
|
|
|
`export let ${name}: ${type};`
|
|
|
|
|
])
|
|
|
|
|
: []),
|
|
|
|
|
...(contextModule ? contextModule.map(({ js, ts }) => [js, ts]) : [])
|
|
|
|
|
]);
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Switch the *.svelte file to use TS
|
|
|
|
|
function updateSvelteFiles() {
|
2020-12-26 02:22:37 +02:00
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
view: "components/Nav",
|
|
|
|
|
vars: [{ name: "segment", type: "string" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
view: "routes/_layout",
|
|
|
|
|
vars: [{ name: "segment", type: "string" }]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
view: "routes/_error",
|
|
|
|
|
vars: [
|
|
|
|
|
{ name: "status", type: "number" },
|
|
|
|
|
{ name: "error", type: "Error" }
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
view: "routes/blog/index",
|
|
|
|
|
vars: [
|
|
|
|
|
{ name: "posts", type: "{ id: string; title: string, html: any }[]" }
|
|
|
|
|
],
|
|
|
|
|
contextModule: [
|
|
|
|
|
{
|
|
|
|
|
js: ".then(r => r.json())",
|
|
|
|
|
ts: ".then((r: { json: () => any; }) => r.json())"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
js: ".then(posts => {",
|
|
|
|
|
ts: ".then((posts: { id: string; title: string, html: any }[]) => {"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
view: "routes/blog/[id]",
|
|
|
|
|
vars: [
|
|
|
|
|
{ name: "post", type: "{ id: string; title: string, html: any }" }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
].forEach(updateSingleSvelteFile);
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateRollupConfig() {
|
2020-12-26 02:22:37 +02:00
|
|
|
// Edit rollup config
|
|
|
|
|
replaceInFile(path.join(projectRoot, "rollup.config.js"), [
|
|
|
|
|
// Edit imports
|
|
|
|
|
[
|
|
|
|
|
/'rollup-plugin-terser';\n(?!import sveltePreprocess)/,
|
|
|
|
|
`'rollup-plugin-terser';
|
2020-12-24 20:32:05 +02:00
|
|
|
import sveltePreprocess from 'svelte-preprocess';
|
2020-12-26 02:22:37 +02:00
|
|
|
import typescript from '@rollup/plugin-typescript';`
|
|
|
|
|
],
|
|
|
|
|
// Edit inputs
|
|
|
|
|
[
|
|
|
|
|
/(?<!THIS_IS_UNDEFINED[^\n]*\n\s*)onwarn\(warning\);/,
|
|
|
|
|
`(warning.code === 'THIS_IS_UNDEFINED') ||\n\tonwarn(warning);`
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
/input: config.client.input\(\)(?!\.replace)/,
|
|
|
|
|
`input: config.client.input().replace(/\.js$/, '.ts')`
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
/input: config.server.input\(\)(?!\.replace)/,
|
|
|
|
|
`input: { server: config.server.input().server.replace(/\.js$/, ".ts") }`
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
/input: config.serviceworker.input\(\)(?!\.replace)/,
|
|
|
|
|
`input: config.serviceworker.input().replace(/\.js$/, '.ts')`
|
|
|
|
|
],
|
|
|
|
|
// Add preprocess to the svelte config, this is tricky because there's no easy signifier.
|
|
|
|
|
// Instead we look for 'hydratable: true,'
|
|
|
|
|
[
|
|
|
|
|
/hydratable: true(?!,\n\s*preprocess)/g,
|
|
|
|
|
"hydratable: true,\n\t\t\t\tpreprocess: sveltePreprocess()"
|
|
|
|
|
],
|
|
|
|
|
// Add TypeScript
|
|
|
|
|
[
|
|
|
|
|
/commonjs\(\)(?!,\n\s*typescript)/g,
|
|
|
|
|
"commonjs(),\n\t\t\ttypescript({ sourceMap: dev })"
|
|
|
|
|
]
|
|
|
|
|
]);
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateServiceWorker() {
|
2020-12-26 02:22:37 +02:00
|
|
|
replaceInFile(path.join(projectRoot, "src", "service-worker.ts"), [
|
|
|
|
|
[`shell.concat(files);`, `(shell as string[]).concat(files as string[]);`],
|
|
|
|
|
[
|
|
|
|
|
`'install', event =>`,
|
|
|
|
|
`'install', <EventType extends ExtendableEvent>(event: EventType) =>`
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
`self.skipWaiting();`,
|
|
|
|
|
`((self as any) as ServiceWorkerGlobalScope).skipWaiting();`
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
`'activate', event =>`,
|
|
|
|
|
`'activate', <EventType extends ExtendableEvent>(event: EventType) =>`
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
`self.clients.claim();`,
|
|
|
|
|
`((self as any) as ServiceWorkerGlobalScope).clients.claim();`
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
`'fetch', event =>`,
|
|
|
|
|
`'fetch', <EventType extends FetchEvent>(event: EventType) =>`
|
|
|
|
|
]
|
|
|
|
|
]);
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createTsConfig() {
|
2020-12-26 02:22:37 +02:00
|
|
|
const tsconfig = `{
|
2020-12-24 20:32:05 +02:00
|
|
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
|
|
|
|
"compilerOptions": {
|
|
|
|
|
"lib": ["DOM", "ES2017", "WebWorker"]
|
|
|
|
|
},
|
|
|
|
|
"include": ["src/**/*", "src/node_modules/**/*"],
|
|
|
|
|
"exclude": ["node_modules/*", "__sapper__/*", "static/*"]
|
|
|
|
|
}`;
|
|
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
createFile(path.join(projectRoot, "tsconfig.json"), tsconfig);
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Adds the extension recommendation
|
|
|
|
|
function configureVsCode() {
|
2020-12-26 02:22:37 +02:00
|
|
|
const dir = path.join(projectRoot, ".vscode");
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
if (!fs.existsSync(dir)) {
|
|
|
|
|
fs.mkdirSync(dir);
|
|
|
|
|
}
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
createFile(
|
|
|
|
|
path.join(projectRoot, ".vscode", "extensions.json"),
|
|
|
|
|
`{"recommendations": ["svelte.svelte-vscode"]}`
|
|
|
|
|
);
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function deleteThisScript() {
|
2020-12-26 02:22:37 +02:00
|
|
|
fs.unlinkSync(path.join(__filename));
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
// Check for Mac's DS_store file, and if it's the only one left remove it
|
|
|
|
|
const remainingFiles = fs.readdirSync(path.join(__dirname));
|
|
|
|
|
if (remainingFiles.length === 1 && remainingFiles[0] === ".DS_store") {
|
|
|
|
|
fs.unlinkSync(path.join(__dirname, ".DS_store"));
|
|
|
|
|
}
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
// Check if the scripts folder is empty
|
|
|
|
|
if (fs.readdirSync(path.join(__dirname)).length === 0) {
|
|
|
|
|
// Remove the scripts folder
|
|
|
|
|
fs.rmdirSync(path.join(__dirname));
|
|
|
|
|
}
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
console.log("Adding TypeScript with Rollup...");
|
2020-12-24 20:32:05 +02:00
|
|
|
|
|
|
|
|
addDepsToPackageJson();
|
|
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
changeJsExtensionToTs(path.join(projectRoot, "src"));
|
2020-12-24 20:32:05 +02:00
|
|
|
|
|
|
|
|
updateSvelteFiles();
|
|
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
updateRollupConfig();
|
2020-12-24 20:32:05 +02:00
|
|
|
|
|
|
|
|
updateServiceWorker();
|
|
|
|
|
|
|
|
|
|
createTsConfig();
|
|
|
|
|
|
|
|
|
|
configureVsCode();
|
|
|
|
|
|
|
|
|
|
// Delete this script, but not during testing
|
|
|
|
|
if (!argv[2]) {
|
2020-12-26 02:22:37 +02:00
|
|
|
deleteThisScript();
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|
|
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
console.log("Converted to TypeScript.");
|
2020-12-24 20:32:05 +02:00
|
|
|
|
2020-12-26 02:22:37 +02:00
|
|
|
if (fs.existsSync(path.join(projectRoot, "node_modules"))) {
|
|
|
|
|
console.log(`\nYou will need to re-run 'npm install' to get started.`);
|
2020-12-24 20:32:05 +02:00
|
|
|
}
|