diff --git a/components/common/typescript.gni b/components/common/typescript.gni index 1ca1814853d..72677330ff0 100644 --- a/components/common/typescript.gni +++ b/components/common/typescript.gni @@ -28,6 +28,8 @@ brave_common_web_compile_inputs = [ # webpack config changes warrant a re-build of all webpack builds rebase_path("../webpack/webpack.config.js"), + rebase_path("../webpack/gen-webpack-grd.js"), + rebase_path("../webpack/path-map.js"), rebase_path("../webpack/webpack-plugin-depfile.js"), # typescript config changes warrant a re-build of all typescript builds @@ -83,6 +85,7 @@ template("transpile_web_ui") { # make sure rebuilds when common files change inputs = brave_common_web_compile_inputs + inputs += [ "$root_gen_dir/tsconfig-webpack.json" ] if (defined(invoker.inputs)) { inputs += invoker.inputs } @@ -90,7 +93,7 @@ template("transpile_web_ui") { deps = [ # Ensure chrome://resources/js file and typescript definitions are # generated - "//ui/webui/resources:library", + "//brave/components/webpack:generate_tsconfig", ] if (defined(invoker.deps)) { deps += invoker.deps diff --git a/components/webpack/BUILD.gn b/components/webpack/BUILD.gn new file mode 100644 index 00000000000..29d66e1f78d --- /dev/null +++ b/components/webpack/BUILD.gn @@ -0,0 +1,14 @@ +action("generate_tsconfig") { + script = "//brave/script/generate-tsconfig.py" + deps = [ + # Ensure chrome://resources/js file and typescript definitions are + # generated + "//ui/webui/resources:library", + ] + inputs = [ + "gen-tsconfig.js", + "path-map.js", + ] + outputs = [ "$root_gen_dir/tsconfig-webpack.json" ] + args = [ "--root_gen_dir=" + rebase_path(root_gen_dir) ] +} diff --git a/components/webpack/gen-tsconfig.js b/components/webpack/gen-tsconfig.js new file mode 100644 index 00000000000..9774a70be68 --- /dev/null +++ b/components/webpack/gen-tsconfig.js @@ -0,0 +1,56 @@ +const fs = require('fs-extra') +const path = require('path') +const pathMap = require('./path-map') + +const srcPath = path.resolve(__dirname, '../../../') +const braveSrcPath = path.join(srcPath, 'brave') + +/** + * Generates a tsconfig.json file in the gen/ directory + * so that typescript can import files from cthe current build's + * gen/ directory (e.g. mojom-generated JS). + * + * @param {*} [atPath=process.env.ROOT_GEN_DIR] + * @returns void + */ + async function createGenTsConfig (atPath = process.env.ROOT_GEN_DIR) { + const configExtendsFrom = path.relative( + atPath, + path.join(braveSrcPath, 'tsconfig-webpack.json') + ) + const tsConfigPath = path.join(atPath, 'tsconfig-webpack.json') + // Even though ts-loader will get the paths from webpack for module resolution + // that does not help some issues where chromium both generates ts definitions + // and has JSDoc comments for the .m.js file. Sometimes the JSDoc is incorrect + // whilst the associated .d.ts file has the correct definition. Without specifying + // the path mapping in the tsconfig.json, Typescript (via ts-loader) will use + // the JSDoc, and fail with an error. The example that prompted this is cr.sendWithPromise + // where Typescript will not see that the second parameter is an optional spread param + // and will fail with an error. Whilst this should be fixed in the chromium source, + // it's better to be explicit here so that developers get the same experience at + // both compile and design time. + const paths = {} + for (const path in pathMap) { + paths[`${path}/*`] = [`${pathMap[path]}/*`] + } + const config = { + extends: configExtendsFrom, + compilerOptions: { + paths + }, + references: [ + { + // This ts project is generated by //ui/webui/resources:library + path: path.join(atPath, 'ui/webui/resources/tsconfig.json') + } + ] + } + await fs.writeFile(tsConfigPath, JSON.stringify(config)) + return tsConfigPath +} + +createGenTsConfig() +.catch(err => { + console.error(err) + process.exit(1) +}) diff --git a/components/webpack/path-map.js b/components/webpack/path-map.js new file mode 100644 index 00000000000..a22b50e67ed --- /dev/null +++ b/components/webpack/path-map.js @@ -0,0 +1,22 @@ +// Copyright (c) 2021 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +const path = require('path') + +module.exports = { +// Find files in the current build configurations /gen directory +'gen': process.env.ROOT_GEN_DIR, +// Generated resources at this path are available at chrome://resources and +// whilst webpack will still bundle, we keep the alias to the served path +// to minimize knowledge of specific gen/ paths and easily allow us to not bundle +// them in the future for certain build configurationa, just like chromium. +'chrome://resources': path.join(process.env.ROOT_GEN_DIR, 'ui/webui/resources/preprocessed'), +// We import brave-ui direct from source and not from package repo, so we need +// direct path to the src/ directory. +'brave-ui': path.resolve(__dirname, '../../node_modules/brave-ui/src'), +// Force same styled-components module for brave-core and brave-ui +// which ensure both repos code use the same singletons, e.g. ThemeContext. +'styled-components': path.resolve(__dirname, '../../node_modules/styled-components'), +} diff --git a/components/webpack/webpack.config.js b/components/webpack/webpack.config.js index eb74c326018..c875266ba66 100644 --- a/components/webpack/webpack.config.js +++ b/components/webpack/webpack.config.js @@ -4,77 +4,14 @@ // you can obtain one at http://mozilla.org/MPL/2.0/. const path = require('path') -const GenerateDepfilePlugin = require('./webpack-plugin-depfile') const fs = require('fs-extra') const webpack = require('webpack') +const GenerateDepfilePlugin = require('./webpack-plugin-depfile') +const pathMap = require('./path-map') -const srcPath = path.resolve(__dirname, '../../../') -const braveSrcPath = path.join(srcPath, 'brave') - -const pathMap = { - // Find files in the current build configurations /gen directory - 'gen': process.env.ROOT_GEN_DIR, - // Generated resources at this path are available at chrome://resources and - // whilst webpack will still bundle, we keep the alias to the served path - // to minimize knowledge of specific gen/ paths and easily allow us to not bundle - // them in the future for certain build configurationa, just like chromium. - 'chrome://resources': path.join(process.env.ROOT_GEN_DIR, 'ui/webui/resources/preprocessed'), - // We import brave-ui direct from source and not from package repo, so we need - // direct path to the src/ directory. - 'brave-ui': path.resolve(__dirname, '../../node_modules/brave-ui/src'), - // Force same styled-components module for brave-core and brave-ui - // which ensure both repos code use the same singletons, e.g. ThemeContext. - 'styled-components': path.resolve(__dirname, '../../node_modules/styled-components'), -} - -/** - * Generates a tsconfig.json file in the gen/ directoryc - * so that typescript can import files from cthe current build's - * gen/ directory (e.g. mojom-generated JS). - * - * @param {*} [atPath=process.env.ROOT_GEN_DIR] - * @returns void - */ -async function createGenTsConfig (atPath = process.env.ROOT_GEN_DIR) { - const configExtendsFrom = path.relative( - atPath, - path.join(braveSrcPath, 'tsconfig-webpack.json') - ) - const tsConfigPath = path.join(atPath, 'tsconfig.json') - // Even though ts-loader will get the paths from webpack for module resolution - // that does not help some issues where chromium both generates ts definitions - // and has JSDoc comments for the .m.js file. Sometimes the JSDoc is incorrect - // whilst the associated .d.ts file has the correct definition. Without specifying - // the path mapping in the tsconfig.json, Typescript (via ts-loader) will use - // the JSDoc, and fail with an error. The example that prompted this is cr.sendWithPromise - // where Typescript will not see that the second parameter is an optional spread param - // and will fail with an error. Whilst this should be fixed in the chromium source, - // it's better to be explicit here so that developers get the same experience at - // both compile and design time. - const paths = {} - for (const path in pathMap) { - paths[`${path}/*`] = [`${pathMap[path]}/*`] - } - const config = { - extends: configExtendsFrom, - compilerOptions: { - paths - }, - references: [ - { - // This ts project is generated by //ui/webui/resources:library - path: path.join(atPath, 'ui/webui/resources/tsconfig.json') - } - ] - } - await fs.writeFile(tsConfigPath, JSON.stringify(config)) - return tsConfigPath -} +const tsConfigPath = path.join(process.env.ROOT_GEN_DIR, 'tsconfig-webpack.json') module.exports = async function (env, argv) { - // TODO(petemill): only do this once per build, in a separate target - // which the webpack targets depend on. - const tsConfigPath = await createGenTsConfig() // Webpack config object return { devtool: argv.mode === 'development' ? '#inline-source-map' : false, @@ -109,9 +46,8 @@ module.exports = async function (env, argv) { options: { getCustomTransformers: path.join(__dirname, './webpack-ts-transformers.js'), allowTsInNodeModules: true, - // TODO(petemill): generate in gen/ directory with baseUrl back to src/brave - // - that would remove any problems with TS analyzing types in an incorrect - // output directory (e.g. Static/ instead of Component/) + // Use generated tsconfig so that we can point at gen/ output in the + // correct build configuration output directory. configFile: tsConfigPath } }, diff --git a/package.json b/package.json index 0ae6de6cbfa..23c77ea5a74 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "pep8": "pycodestyle --max-line-length 120 -r script", "pylint": "node ./build/commands/scripts/commands.js pylint", "web-ui-gen-grd": "node components/webpack/gen-webpack-grd", + "web-ui-gen-tsconfig": "node components/webpack/gen-tsconfig", "web-ui": "webpack --config components/webpack/webpack.config.js --colors", "build-storybook": "build-storybook -c .storybook -o .storybook-out", "storybook": "start-storybook", diff --git a/script/generate-tsconfig.py b/script/generate-tsconfig.py new file mode 100755 index 00000000000..7acba05c016 --- /dev/null +++ b/script/generate-tsconfig.py @@ -0,0 +1,43 @@ +import argparse +import os +import sys +from lib.util import execute_stdout, scoped_cwd + + +NPM = 'npm' +if sys.platform in ['win32', 'cygwin']: + NPM += '.cmd' + + +def main(): + args = parse_args() + root_gen_dir = args.root_gen_dir[0] + generate_tsconfig(root_gen_dir) + + +def parse_args(): + parser = argparse.ArgumentParser(description='Generate tsconfig') + parser.add_argument('--root_gen_dir', nargs=1) + args = parser.parse_args() + # validate args + if (args.root_gen_dir is None or + len(args.root_gen_dir) != 1 or + len(args.root_gen_dir[0]) == 0): + raise Exception("root_gen_dir argument was not specified correctly") + # args are valid + return args + +def generate_tsconfig(root_gen_dir, env=None): + if env is None: + env = os.environ.copy() + + args = [NPM, 'run', 'web-ui-gen-tsconfig'] + + env["ROOT_GEN_DIR"] = root_gen_dir + + dirname = os.path.abspath(os.path.join(__file__, '..', '..')) + with scoped_cwd(dirname): + execute_stdout(args, env) + +if __name__ == '__main__': + sys.exit(main())