The namegen5 website.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

306 lines
9.4 KiB

4 years ago
  1. /**
  2. * Run this script to convert the project to TypeScript. This is only guaranteed to work
  3. * on the unmodified default template; if you have done code changes you are likely need
  4. * to touch up the generated project manually.
  5. */
  6. // @ts-check
  7. const fs = require('fs');
  8. const path = require('path');
  9. const { argv } = require('process');
  10. const projectRoot = argv[2] || path.join(__dirname, '..');
  11. const isRollup = fs.existsSync(path.join(projectRoot, "rollup.config.js"));
  12. function warn(message) {
  13. console.warn('Warning: ' + message);
  14. }
  15. function replaceInFile(fileName, replacements) {
  16. if (fs.existsSync(fileName)) {
  17. let contents = fs.readFileSync(fileName, 'utf8');
  18. let hadUpdates = false;
  19. replacements.forEach(([from, to]) => {
  20. const newContents = contents.replace(from, to);
  21. const isAlreadyApplied = typeof to !== 'string' || contents.includes(to);
  22. if (newContents !== contents) {
  23. contents = newContents;
  24. hadUpdates = true;
  25. } else if (!isAlreadyApplied) {
  26. warn(`Wanted to update "${from}" in ${fileName}, but did not find it.`);
  27. }
  28. });
  29. if (hadUpdates) {
  30. fs.writeFileSync(fileName, contents);
  31. } else {
  32. console.log(`${fileName} had already been updated.`);
  33. }
  34. } else {
  35. warn(`Wanted to update ${fileName} but the file did not exist.`);
  36. }
  37. }
  38. function createFile(fileName, contents) {
  39. if (fs.existsSync(fileName)) {
  40. warn(`Wanted to create ${fileName}, but it already existed. Leaving existing file.`);
  41. } else {
  42. fs.writeFileSync(fileName, contents);
  43. }
  44. }
  45. function addDepsToPackageJson() {
  46. const pkgJSONPath = path.join(projectRoot, 'package.json');
  47. const packageJSON = JSON.parse(fs.readFileSync(pkgJSONPath, 'utf8'));
  48. packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
  49. ...(isRollup ? { '@rollup/plugin-typescript': '^6.0.0' } : { 'ts-loader': '^8.0.4' }),
  50. '@tsconfig/svelte': '^1.0.10',
  51. '@types/compression': '^1.7.0',
  52. '@types/node': '^14.11.1',
  53. '@types/polka': '^0.5.1',
  54. 'svelte-check': '^1.0.46',
  55. 'svelte-preprocess': '^4.3.0',
  56. tslib: '^2.0.1',
  57. typescript: '^4.0.3'
  58. });
  59. // Add script for checking
  60. packageJSON.scripts = Object.assign(packageJSON.scripts, {
  61. validate: 'svelte-check --ignore src/node_modules/@sapper'
  62. });
  63. // Write the package JSON
  64. fs.writeFileSync(pkgJSONPath, JSON.stringify(packageJSON, null, ' '));
  65. }
  66. function changeJsExtensionToTs(dir) {
  67. const elements = fs.readdirSync(dir, { withFileTypes: true });
  68. for (let i = 0; i < elements.length; i++) {
  69. if (elements[i].isDirectory()) {
  70. changeJsExtensionToTs(path.join(dir, elements[i].name));
  71. } else if (elements[i].name.match(/^[^_]((?!json).)*js$/)) {
  72. fs.renameSync(path.join(dir, elements[i].name), path.join(dir, elements[i].name).replace('.js', '.ts'));
  73. }
  74. }
  75. }
  76. function updateSingleSvelteFile({ view, vars, contextModule }) {
  77. replaceInFile(path.join(projectRoot, 'src', `${view}.svelte`), [
  78. [/(?:<script)(( .*?)*?)>/gm, (m, attrs) => `<script${attrs}${!attrs.includes('lang="ts"') ? ' lang="ts"' : ''}>`],
  79. ...(vars ? vars.map(({ name, type }) => [`export let ${name};`, `export let ${name}: ${type};`]) : []),
  80. ...(contextModule ? contextModule.map(({ js, ts }) => [js, ts]) : [])
  81. ]);
  82. }
  83. // Switch the *.svelte file to use TS
  84. function updateSvelteFiles() {
  85. [
  86. {
  87. view: 'components/Nav',
  88. vars: [{ name: 'segment', type: 'string' }]
  89. },
  90. {
  91. view: 'routes/_layout',
  92. vars: [{ name: 'segment', type: 'string' }]
  93. },
  94. {
  95. view: 'routes/_error',
  96. vars: [
  97. { name: 'status', type: 'number' },
  98. { name: 'error', type: 'Error' }
  99. ]
  100. },
  101. {
  102. view: 'routes/blog/index',
  103. vars: [{ name: 'posts', type: '{ slug: string; title: string, html: any }[]' }],
  104. contextModule: [
  105. {
  106. js: '.then(r => r.json())',
  107. ts: '.then((r: { json: () => any; }) => r.json())'
  108. },
  109. {
  110. js: '.then(posts => {',
  111. ts: '.then((posts: { slug: string; title: string, html: any }[]) => {'
  112. }
  113. ]
  114. },
  115. {
  116. view: 'routes/blog/[slug]',
  117. vars: [{ name: 'post', type: '{ slug: string; title: string, html: any }' }]
  118. }
  119. ].forEach(updateSingleSvelteFile);
  120. }
  121. function updateRollupConfig() {
  122. // Edit rollup config
  123. replaceInFile(path.join(projectRoot, 'rollup.config.js'), [
  124. // Edit imports
  125. [
  126. /'rollup-plugin-terser';\n(?!import sveltePreprocess)/,
  127. `'rollup-plugin-terser';
  128. import sveltePreprocess from 'svelte-preprocess';
  129. import typescript from '@rollup/plugin-typescript';
  130. `
  131. ],
  132. // Edit inputs
  133. [
  134. /(?<!THIS_IS_UNDEFINED[^\n]*\n\s*)onwarn\(warning\);/,
  135. `(warning.code === 'THIS_IS_UNDEFINED') ||\n\tonwarn(warning);`
  136. ],
  137. [/input: config.client.input\(\)(?!\.replace)/, `input: config.client.input().replace(/\\.js$/, '.ts')`],
  138. [
  139. /input: config.server.input\(\)(?!\.replace)/,
  140. `input: { server: config.server.input().server.replace(/\\.js$/, ".ts") }`
  141. ],
  142. [
  143. /input: config.serviceworker.input\(\)(?!\.replace)/,
  144. `input: config.serviceworker.input().replace(/\\.js$/, '.ts')`
  145. ],
  146. // Add preprocess
  147. [/compilerOptions/g, 'preprocess: sveltePreprocess({ sourceMap: dev }),\n\t\t\t\tcompilerOptions'],
  148. // Add TypeScript
  149. [/commonjs\(\)(?!,\n\s*typescript)/g, 'commonjs(),\n\t\t\ttypescript({ sourceMap: dev })']
  150. ]);
  151. }
  152. function updateWebpackConfig() {
  153. // Edit webpack config
  154. replaceInFile(path.join(projectRoot, 'webpack.config.js'), [
  155. // Edit imports
  156. [
  157. /require\('webpack-modules'\);\n(?!const sveltePreprocess)/,
  158. `require('webpack-modules');\nconst sveltePreprocess = require('svelte-preprocess');\n`
  159. ],
  160. // Edit extensions
  161. [
  162. /\['\.mjs', '\.js', '\.json', '\.svelte', '\.html'\]/,
  163. `['.mjs', '.js', '.ts', '.json', '.svelte', '.html']`
  164. ],
  165. // Edit entries
  166. [
  167. /entry: config\.client\.entry\(\)/,
  168. `entry: { main: config.client.entry().main.replace(/\\.js$/, '.ts') }`
  169. ],
  170. [
  171. /entry: config\.server\.entry\(\)/,
  172. `entry: { server: config.server.entry().server.replace(/\\.js$/, '.ts') }`
  173. ],
  174. [
  175. /entry: config\.serviceworker\.entry\(\)/,
  176. `entry: { 'service-worker': config.serviceworker.entry()['service-worker'].replace(/\\.js$/, '.ts') }`
  177. ],
  178. // Add preprocess to the svelte config, this is tricky because there's no easy signifier.
  179. // Instead we look for 'hydratable: true,'
  180. [
  181. /hydratable: true(?!,\n\s*preprocess)/g,
  182. 'hydratable: true,\n\t\t\t\t\t\t\tpreprocess: sveltePreprocess({ sourceMap: dev })'
  183. ],
  184. // Add TypeScript rules for client and server
  185. [
  186. /module: {\n\s*rules: \[\n\s*(?!{\n\s*test: \/\\\.ts\$\/)/g,
  187. `module: {\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttest: /\\.ts$/,\n\t\t\t\t\tloader: 'ts-loader'\n\t\t\t\t},\n\t\t\t\t`
  188. ],
  189. // Add TypeScript rules for serviceworker
  190. [
  191. /output: config\.serviceworker\.output\(\),\n\s*(?!module)/,
  192. `output: config.serviceworker.output(),\n\t\tmodule: {\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttest: /\\.ts$/,\n\t\t\t\t\tloader: 'ts-loader'\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t`
  193. ],
  194. // Edit outputs
  195. [
  196. /output: config\.serviceworker\.output\(\),\n\s*(?!resolve)/,
  197. `output: config.serviceworker.output(),\n\t\tresolve: { extensions: ['.mjs', '.js', '.ts', '.json'] },\n\t\t`
  198. ]
  199. ]);
  200. }
  201. function updateServiceWorker() {
  202. replaceInFile(path.join(projectRoot, 'src', 'service-worker.ts'), [
  203. [`shell.concat(files);`, `(shell as string[]).concat(files as string[]);`],
  204. [`self.skipWaiting();`, `((self as any) as ServiceWorkerGlobalScope).skipWaiting();`],
  205. [`self.clients.claim();`, `((self as any) as ServiceWorkerGlobalScope).clients.claim();`],
  206. [`fetchAndCache(request)`, `fetchAndCache(request: Request)`],
  207. [`self.addEventListener('activate', event =>`, `self.addEventListener('activate', (event: ExtendableEvent) =>`],
  208. [`self.addEventListener('install', event =>`, `self.addEventListener('install', (event: ExtendableEvent) =>`],
  209. [`addEventListener('fetch', event =>`, `addEventListener('fetch', (event: FetchEvent) =>`],
  210. ]);
  211. }
  212. function createTsConfig() {
  213. const tsconfig = `{
  214. "extends": "@tsconfig/svelte/tsconfig.json",
  215. "compilerOptions": {
  216. "lib": ["DOM", "ES2017", "WebWorker"]
  217. },
  218. "include": ["src/**/*", "src/node_modules/**/*"],
  219. "exclude": ["node_modules/*", "__sapper__/*", "static/*"]
  220. }`;
  221. createFile(path.join(projectRoot, 'tsconfig.json'), tsconfig);
  222. }
  223. // Adds the extension recommendation
  224. function configureVsCode() {
  225. const dir = path.join(projectRoot, '.vscode');
  226. if (!fs.existsSync(dir)) {
  227. fs.mkdirSync(dir);
  228. }
  229. createFile(path.join(projectRoot, '.vscode', 'extensions.json'), `{"recommendations": ["svelte.svelte-vscode"]}`);
  230. }
  231. function deleteThisScript() {
  232. fs.unlinkSync(path.join(__filename));
  233. // Check for Mac's DS_store file, and if it's the only one left remove it
  234. const remainingFiles = fs.readdirSync(path.join(__dirname));
  235. if (remainingFiles.length === 1 && remainingFiles[0] === '.DS_store') {
  236. fs.unlinkSync(path.join(__dirname, '.DS_store'));
  237. }
  238. // Check if the scripts folder is empty
  239. if (fs.readdirSync(path.join(__dirname)).length === 0) {
  240. // Remove the scripts folder
  241. fs.rmdirSync(path.join(__dirname));
  242. }
  243. }
  244. console.log(`Adding TypeScript with ${isRollup ? "Rollup" : "webpack" }...`);
  245. addDepsToPackageJson();
  246. changeJsExtensionToTs(path.join(projectRoot, 'src'));
  247. updateSvelteFiles();
  248. if (isRollup) {
  249. updateRollupConfig();
  250. } else {
  251. updateWebpackConfig();
  252. }
  253. updateServiceWorker();
  254. createTsConfig();
  255. configureVsCode();
  256. // Delete this script, but not during testing
  257. if (!argv[2]) {
  258. deleteThisScript();
  259. }
  260. console.log('Converted to TypeScript.');
  261. if (fs.existsSync(path.join(projectRoot, 'node_modules'))) {
  262. console.log(`
  263. Next:
  264. 1. run 'npm install' again to install TypeScript dependencies
  265. 2. run 'npm run build' for the @sapper imports in your project to work
  266. `);
  267. }