Tim van der Lippe | 20e5ff2 | 2021-04-06 15:15:02 | [diff] [blame] | 1 | # Architecture of DevTools |
| 2 | |
| 3 | This document explains the high-level architecture as well as any considerations that were made along the way. |
| 4 | The document is evolving and will be updated whenever architectural changes are being made. |
| 5 | |
| 6 | ## Guiding principles |
| 7 | |
| 8 | Throughout this document, references are included to relevant [Web Platform Design Principles], whenever they are applicable for that specific section. |
| 9 | It is recommended to be familiar with the Web Platform Design Principles prior to reading to this document, but it is not required. |
| 10 | There are additional DevTools-specific guiding principles that are listed in this section. |
| 11 | |
| 12 | ### Load only what is necessary |
| 13 | |
| 14 | DevTools is a large web application. |
| 15 | It contains dozens of features, most of them are distinct. |
| 16 | As such, loading all features up front is infeasible and can lead to large startup times of DevTools. |
| 17 | The DevTools architecture should encourage granular implementations of features, lazy loading the minimal amount of code to make features work. |
| 18 | |
| 19 | See also: |
| 20 | * [Put user needs first] |
| 21 | |
| 22 | ### Prefer web platform features whenever possible |
| 23 | |
| 24 | DevTools ships as part of Chromium-based browsers and therefore is long-living. |
| 25 | Code that is shipped today can live on for years, even decades. |
| 26 | Therefore, web best practices constantly evolve during the lifespan of DevTools. |
| 27 | To avoid frequent rewrites of features, each feature should be implemented with longevity in mind. |
| 28 | Web platform features are standardized and designed to be supported ad infinitum. |
| 29 | Whenever possible, prefer the usage of web platform features over custom solutions, as custom solutions require constant maintenance and are more likely to become out-of-date. |
| 30 | |
| 31 | See also: |
| 32 | * [Prefer simple solutions] |
| 33 | * [Put user needs first] |
| 34 | |
| 35 | ### Design with continuous deployment in mind |
| 36 | |
| 37 | DevTools ships every single day in Canary builds of Chromium-based browsers. |
| 38 | It is therefore risky to halt development during a migration (even for a couple of weeks), as DevTools can cause Canary builds to break and effect not just end-users, but also engineers working on the web platform itself. |
| 39 | The symbiosis of the web platform and DevTools means that DevTools itself must be kept up-to-date, to support a continuously evolving platform. |
| 40 | |
| 41 | Migrations should therefore be gradual and allow for continuous deployment of DevTools in Canary builds. |
| 42 | The migrations will thus have to take into account not just the desired end solution, but also the limitations of today's implementation. |
| 43 | In the end, it is not possible to predict when migrations are completed, which means that the codebase can be under migration for a significant amount of time. |
| 44 | Ensure that migrations do not (strongly) negatively impact feature development and evolution of the wider web platform and can be completed in a timely fashion. |
| 45 | |
| 46 | See also: |
| 47 | * [Put user needs first] |
| 48 | * [Prefer web platform features whenever possible] |
| 49 | |
| 50 | ### Use flexible third_party libraries whenever necessary |
| 51 | |
| 52 | Not all requirements of DevTools can be fulfilled by web platform features alone. |
| 53 | There will be situations in which third_party libraries (ideally closely built on top of web platform features) are the appropriate solution. |
| 54 | Every third_party library introduced in DevTools adds risk to the longterm maintenance of the overall product. |
| 55 | Therefore, each third_party library that DevTools uses should be flexible: a library should be (relatively) easy to be removed from the product. |
| 56 | |
| 57 | In practice, this means that any new third_party library must allow for gradual introduction in the codebase, but (if required) also gradually removal. |
| 58 | Since third_party libraries can become unmaintained, gradual removal allows continued development of DevTools features, while the impact of the deprecation is dealt with. |
| 59 | If a third_party library is difficult to remove and has a broad impact on the overall codebase, it could cause a halt of development of DevTools features. |
| 60 | Since the web platform is continuously evolving and DevTools is a part of the platform focused on web developers, halting feature development can have a negative impact on the wider web platform. |
| 61 | |
| 62 | Concretely, the introduction of a framework that takes control of the lifecycle of (parts of) DevTools is practically impossible. |
| 63 | Such frameworks require difficult-to-execute migrations and typically don't allow for gradual removals. |
| 64 | Moreover, decisions made by maintainers of third_party frameworks could cause significant maintenance churn for DevTools maintainers. |
| 65 | |
| 66 | Note that in this section, the definition of "framework" can differ based on point-of-views of stakeholders and could apply more broadly than initially expected. |
| 67 | Make sure to evaluate third_party packages based on impact on the DevTools codebase, which could be larger than third_party maintainers might have intended. |
| 68 | In other words: even if a third_party package is advertised as a library, it could still be considered as a framework from the perspective of DevTools maintainers. |
| 69 | |
| 70 | See also: |
| 71 | * [Load only what is necessary] |
| 72 | * [Design with continuous deployment in mind] |
| 73 | |
| 74 | ### Limit implementation possibilities while providing maximum flexibility |
| 75 | |
| 76 | Typically, there are multiples ways to implement application features on the web. |
| 77 | A direct result of the flexibility of the web is the proliferation of different solutions to the same problem. |
| 78 | A negative consequence of the flexibility is the wide variety of solutions and corresponding maintenance cost in the longterm future. |
| 79 | The DevTools architecture should limit the amount of possible solutions to various problems, yet providing maximum flexibility to engineers implementing DevTools features. |
| 80 | |
| 81 | Sadly, that is easier said than done. |
| 82 | Even when taking this principle into account when working on DevTools' architecture, it can be relatively easy to discover "architectural regressions" years later. |
| 83 | On the flipside, it can be appealing to be overly restrictive, to avoid such "architectural regressions". |
| 84 | However, "unnecessary" (this qualification can be subjective and differ based on point-of-view) restrictions can have a strong negative impact on development of DevTools features and therefore can cause more problems on its own. |
| 85 | |
| 86 | Balancing the architectural requirements to ensure a stable and fast-loading DevTools versus the needs of implementing new DevTools features is a continuously evolving process. |
| 87 | To ensure a healthy balance, a periodic evaluation can be useful to address potential architectural technical debt. |
| 88 | |
| 89 | See also: |
| 90 | * [Prefer simple solutions] |
| 91 | * [Load only what is necessary] |
| 92 | |
| 93 | # Build system |
| 94 | |
| 95 | Since DevTools is a complex application that integrates with Chromium, it is built on top of the Chromium build system [GN] built on top of [Ninja]. |
| 96 | The build system ensures that all relevant files are correctly defined for consumption by Chromium. |
| 97 | It also integrates with (third_party) tooling such as a type checker ([TypeScript]) and upstream Chromium packages ([Chrome DevTools Protocol]). |
| 98 | |
| 99 | DevTools-specific GN templates are defined in [scripts/build/ninja/](scripts/build/ninja/). |
| 100 | Chromium consumes the output of the DevTools GN target [:generate_devtools_grd defined in BUILD.gn](BUILD.gn), which generates a GRD bundle using [GRIT]. |
| 101 | Detailed descriptions about each template are included in [scripts/build/ninja/README.md](scripts/build/ninja/README.md). |
| 102 | |
| 103 | # Startup process overview |
| 104 | |
| 105 | DevTools startup process is based on a core-feature model. |
| 106 | The application builds on top of a core of common functionality that is shared between features. |
| 107 | The core is responsible for communicating with the Chromium backend and building a foundation of UI functionality to facilitate the definition of higher-level panels containing features. |
| 108 | Each feature is declared upfront and lazily loaded whenever the user interacts with the feature [[Load only what is necessary]]. |
| 109 | |
| 110 | Loading of core functionality and features is built on top of [JavaScript modules]. |
| 111 | Core functionality is loaded via static imports, while implementations of features is lazily loaded using [dynamic imports]. |
| 112 | Features themselves use static imports for loading core and feature-specific functionality. |
| 113 | |
Benedikt Meurer | 69fc30e | 2022-09-01 07:11:05 | [diff] [blame] | 114 |  |
Tim van der Lippe | 20e5ff2 | 2021-04-06 15:15:02 | [diff] [blame] | 115 | |
| 116 | Enforcement of the rules regarding loading is implemented using the [ESLint] rule defined in [scripts/eslint_rules/lib/es_modules_import.js](scripts/eslint_rules/lib/es_modules_import.js). |
| 117 | |
| 118 | ## DevTools application entrypoints |
| 119 | |
| 120 | There are multiple variants of the DevTools application. |
Andrés Olivares | 0650b4a | 2022-10-19 10:16:59 | [diff] [blame] | 121 | The main DevTools application is [devtools_app.ts](../front_end/entrypoints/devtools_app/devtools_app.ts), which developers can open via F12 or "Right click -> Inspect element" in their Chromium browser. |
| 122 | Other application entrypoints are a dedicated tool for debugging Node on which multiple targets can be inspected ([node_app.ts](../front_end/entrypoints/node_app/node_app.ts)), a non-browser context debugger for V8 targets ([js_app.ts](../front_end/entrypoints/js_app/js_app.ts)), a (service) worker context ([worker_app.ts](../front_end/entrypoints/worker_app/worker_app.ts)), remote debugging of (Android) devices ([inspector.ts](../front_end/entrypoints/inspector/inspector.ts)) and a basic version of DevTools which was initially intended for non-browser targets but doesn't have any particular use case that differentiates it from the other variants ([ndb_app.ts](../front_end/entrypoints/ndb_app/ndb_app.ts)). |
Tim van der Lippe | 20e5ff2 | 2021-04-06 15:15:02 | [diff] [blame] | 123 | |
Andrés Olivares | 0650b4a | 2022-10-19 10:16:59 | [diff] [blame] | 124 | Each application entrypoint has a corresponding `.html` file with the same name, that is automatically in the frontend generated and can be loaded by the Chromium DevTools backend. |
Tim van der Lippe | 20e5ff2 | 2021-04-06 15:15:02 | [diff] [blame] | 125 | The JavaScript files import the relevant "meta" files containing the declarations of DevTools features. |
| 126 | |
| 127 | ## Upfront declaration of DevTools features |
| 128 | |
| 129 | The upfront declaration of DevTools features is also known as "extensions". |
| 130 | Extensions add functionality to the DevTools application using declarative registration calls. |
| 131 | |
| 132 | There are multiple types of extensions, including how DevTools handles its own internal business logic or to declare user-facing features with localized strings. |
| 133 | There are 4 main types of extensions: |
| 134 | |
| 135 | * [UI.ActionRegistration.Action](front_end/ui/ActionRegistration.ts) |
| 136 | * [UI.View.View](front_end/ui/View.js) |
| 137 | * [Common.Settings.Setting](front_end/common/Settings.js) |
| 138 | * General type lookups. |
| 139 | |
| 140 | Each specific extension is documented in the README of their respective folder. |
| 141 | |
| 142 | The registration of a particular extension implemented in `module` must always be declared in a `<module>-meta.ts` in the same folder. |
| 143 | The meta files should be included by all relevant DevTools application entrypoints. |
| 144 | If you want to make functionality available in all DevTools application entrypoints, you can import it in [shell.js](front_end/shell.js). |
| 145 | |
| 146 | For example, the meta declaration file for [front_end/network/](front_end/network/) is called [front_end/network/network-meta.ts](front_end/network/network-meta.ts) and defined in [front_end/network/BUILD.gn](front_end/network/BUILD.gn): |
| 147 | |
| 148 | ```python |
| 149 | devtools_entrypoint("meta") { |
| 150 | entrypoint = "network-meta.ts" |
| 151 | deps = [ |
| 152 | ":bundle", |
| 153 | "../root:bundle", |
| 154 | "../ui:bundle", |
| 155 | ] |
| 156 | } |
| 157 | ``` |
| 158 | |
| 159 | Below is an example implementation of a `<module>-meta.ts` (using [front_end/network/network-meta.ts](front_end/network/network-meta.ts) as running example). |
| 160 | For information about the localization system, please see the documentation in [docs/localization/](docs/localization/). |
| 161 | |
| 162 | ```ts |
| 163 | // Any static imports on core modules |
| 164 | import * as Common from '../common/common.js'; |
| 165 | import * as UI from '../ui/ui.js'; |
| 166 | |
| 167 | // A type-import that is removed during compilation by TypeScript. Therefore, |
| 168 | // it is not a static import on runtime and adheres to the lazy loading |
| 169 | // rules defined for the startup process. |
| 170 | import type * as Network from './network.js'; |
| 171 | |
| 172 | import * as i18n from '../i18n/i18n.js'; |
| 173 | const UIStrings = { |
| 174 | // UIStrings definitions here |
| 175 | }; |
| 176 | const str_ = i18n.i18n.registerUIStrings('network/network-meta.ts', UIStrings); |
| 177 | // Since meta files are loaded synchronously during startup, the localization system |
| 178 | // has not finished loading yet and we need to lazily compute localized strings. |
| 179 | const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_); |
| 180 | // The result of the dynamically loaded module. Will be undefined until the user has |
| 181 | // started using a feature defined in this module. |
| 182 | let loadedNetworkModule: (typeof Network|undefined); |
| 183 | |
| 184 | // Lazily load the functionality for the network panel. This function will only dynamically |
| 185 | // import the module once. |
| 186 | async function loadNetworkModule(): Promise<typeof Network> { |
| 187 | if (!loadedNetworkModule) { |
| 188 | // Dynamic import to load `front_end/network/network.ts`, which contains the |
| 189 | // actual implementation of the Network panel. |
| 190 | loadedNetworkModule = await import('./network.js'); |
| 191 | } |
| 192 | return loadedNetworkModule; |
| 193 | } |
| 194 | |
| 195 | // Retrieve any context types from the lazily loaded module, but only if the module has |
| 196 | // already been loaded at least once. The context types are used to determine the availibility |
| 197 | // of certain DevTools extensions, for example certain feature-specific command menu entries |
| 198 | // like the debugger actions when debugging source code. For more information, see the usage |
| 199 | // of this function down below. |
| 200 | function maybeRetrieveContextTypes<T = unknown>(getClassCallBack: (loadedNetworkModule: typeof Network) => T[]): T[] { |
| 201 | if (loadedNetworkModule === undefined) { |
| 202 | return []; |
| 203 | } |
| 204 | return getClassCallBack(loadedNetworkModule); |
| 205 | } |
| 206 | |
| 207 | // A top-level panel. This is the main extension for a high-level feature. The panel is |
| 208 | // loaded whenever the user clicks on the panel tab or loads it via the "More Tools" menu. |
| 209 | // For more information about view extensions, please see the documentation in |
| 210 | // `front_end/ui/View.js`. |
| 211 | UI.ViewManager.registerViewExtension({ |
| 212 | location: UI.ViewManager.ViewLocationValues.PANEL, |
| 213 | id: 'network', |
| 214 | commandPrompt: i18nLazyString(UIStrings.showNetwork), |
| 215 | title: i18nLazyString(UIStrings.network), |
| 216 | order: 40, |
| 217 | // This function is executed by the `ViewManager` (defined in `front_end/ui/ViewManager.js`) |
| 218 | // whenever the user requests the panel to be loaded. |
| 219 | async loadView() { |
| 220 | // Lazily load the module, if we hadn't loaded it already. |
| 221 | const Network = await loadNetworkModule(); |
| 222 | // Obtain a singleton reference to the network panel. We don't allow multiple instances |
| 223 | // of the same panel, to ensure a performant application. |
| 224 | return Network.NetworkPanel.NetworkPanel.instance(); |
| 225 | }, |
| 226 | }); |
| 227 | |
| 228 | // A keybinding that is only active when the network panel is open and visible for |
| 229 | // the user. It can hide the detailed network request information when the user has |
| 230 | // clicked on a specific network request. |
| 231 | // For more information about action extensions, please see the documentation in |
| 232 | // `front_end/ui/ActionRegistration.ts`. |
| 233 | UI.ActionRegistration.registerActionExtension({ |
| 234 | actionId: 'network.hide-request-details', |
| 235 | category: UI.ActionRegistration.ActionCategory.NETWORK, |
| 236 | title: i18nLazyString(UIStrings.hideRequestDetails), |
| 237 | // If this function is not defined, this action is considered global. |
| 238 | // For more detailed documentation about the definition and usage of `contextTypes`, |
| 239 | // please see the `ActionRegistration` interface. |
| 240 | contextTypes() { |
| 241 | // This will return an array of relevant context types that should be loaded |
| 242 | // and visible to the user for this action to be available in the command |
| 243 | // menu or when the corresponding keybinding is invoked. |
| 244 | return maybeRetrieveContextTypes(Network => [Network.NetworkPanel.NetworkPanel]); |
| 245 | }, |
| 246 | // Just like the network panel, lazily load the action definition when requested by |
| 247 | // the user. |
| 248 | async loadActionDelegate() { |
| 249 | const Network = await loadNetworkModule(); |
| 250 | return Network.NetworkPanel.ActionDelegate.instance(); |
| 251 | }, |
| 252 | bindings: [ |
| 253 | { |
| 254 | shortcut: 'Esc', |
| 255 | }, |
| 256 | ], |
| 257 | }); |
| 258 | ``` |
| 259 | |
| 260 | The ":meta" `devtools_entrypoint` is added as a dependency to all DevTools application entrypoints defined in [front_end/BUILD.gn](front_end/BUILD.gn). |
| 261 | |
Tim van der Lippe | 92bf1da | 2021-04-30 11:24:54 | [diff] [blame] | 262 | # Folder structure |
| 263 | |
| 264 | DevTools frontend is divided in multiple folders: |
| 265 | |
| 266 | - `core/` includes code that can be used by any other module. |
| 267 | It typically includes utility functions as well as integration with backend. |
| 268 | - `models/` includes business logic and handling of data received from the backend. |
| 269 | - `panels/` includes high-level panels and top-level features. |
| 270 | Each panel typically maps to a separate panel in DevTools, but some panels are integrated into others. |
| 271 | - `ui/components/` includes reusable components that can be used to build multiple panels. |
| 272 | - `ui/legacy/components/` includes legacy components. |
| 273 | Please favor using `ui/components` wherever possible. |
| 274 | - `entrypoints/` includes all entrypoints of DevTools, which can compose a variety of DevTools panels. |
| 275 | |
| 276 | In general, the following structure is applicable to dependencies between modules: |
| 277 | |
Benedikt Meurer | 69fc30e | 2022-09-01 07:11:05 | [diff] [blame] | 278 |  |
Tim van der Lippe | 92bf1da | 2021-04-30 11:24:54 | [diff] [blame] | 279 | |
| 280 | - `core/` can be imported by any module |
| 281 | - `models/` can be imported by `panels/` and `entrypoints/` |
| 282 | - `ui/` can be imported by `panels/` and `entrypoints/` |
| 283 | - `panels/` can be imported by `entrypoints/` |
| 284 | |
| 285 | To enforce module imports adhere to these rules, actions specify their [GN `visibility` rules]. |
| 286 | An example bundle that is defined in `models/workspace_diff/BUILD.gn`: |
| 287 | |
| 288 | ```python |
| 289 | devtools_entrypoint("bundle") { |
| 290 | entrypoint = "workspace_diff.ts" |
| 291 | |
| 292 | deps = [ ":workspace_diff" ] |
| 293 | |
| 294 | visibility = [ |
| 295 | # Allow importing in this same module |
| 296 | ":*", |
| 297 | # Only these two panels are allowed to use `workspace_diff` |
| 298 | "../../panels/changes/*", |
| 299 | "../../panels/sources/*", |
| 300 | ] |
| 301 | |
| 302 | # Used to allow overrides for downstream projects. Please see below for more information |
| 303 | visibility += devtools_models_visibility |
| 304 | } |
| 305 | ``` |
| 306 | |
| 307 | There are downstream projects that either have forked DevTools or build on top of DevTools. |
| 308 | By defining visibility rules using relative paths, in downstream projects there would be no way to update the visibility to allow for additional modules to be importing a module. |
| 309 | For example, if a downstream project defines a new panel and it would need to depend on `workspace_diff`, it would need to change the visibility definition of `workspace_diff`, which it can't. |
| 310 | |
| 311 | To allow for additional visibility rules to be defined, any target in DevTools has to allow for overrides. |
| 312 | These overrides are typically defined in `visibility.gni` files. |
| 313 | For example, `models/visibility.gni` defines the following: |
| 314 | |
| 315 | ```python |
| 316 | declare_args() { |
| 317 | devtools_models_visibility = [] |
| 318 | } |
| 319 | ``` |
| 320 | |
| 321 | By declaring it as an GN arg, downstream projects can override their GN arg in their `/.gn` file. |
| 322 | For example, `/.gn` could declare the following: |
| 323 | |
| 324 | ```python |
| 325 | default_args = { |
| 326 | devtools_models_visibility = [ |
| 327 | "//front_end/my/new/panel/that/depends/on/workspace_diff/*", |
| 328 | ] |
| 329 | } |
| 330 | ``` |
| 331 | |
| 332 | Now, the new panel is allowed to add a dependency edge on `models/workspace_diff:bundle`. |
| 333 | |
| 334 | # DevTools GRD integration |
| 335 | |
| 336 | To bundle DevTools with Chromium, DevTools builds its GRD file that will be consumed by [GRIT]. |
| 337 | The GRD file lists all required files that should be loaded either in Debug or Release mode. |
| 338 | All files that should be bundled are listed in `config/gni/devtools_grd_files.gni`. |
Mathias Bynens | 6f80ee6 | 2021-05-03 06:50:24 | [diff] [blame] | 339 | If a file should be present in debug and release mode, add the file to `grd_files_release_sources`. |
| 340 | If a file should only be present in debug mode, add the file to `grd_files_debug_sources`. |
Tim van der Lippe | 92bf1da | 2021-04-30 11:24:54 | [diff] [blame] | 341 | |
| 342 | Note that `devtools_module` and `devtools_entrypoint` automatically take care of this for you. |
| 343 | Any file included in `devtools_module` is only present in the Debug GRD file. |
| 344 | Any file included as entrypoint of `devtools_entrypoint` is present in both the Release and Debug GRD file. |
| 345 | |
| 346 | There are additional actions that can add files to the GRD file, for example images or Markdown files. |
| 347 | To allow for any action to define any relevant GRD file inclusions, DevTools uses [GN `metadata` definitions]. |
| 348 | For a custom action, specify the following: |
| 349 | |
| 350 | ```python |
| 351 | node_action("generate_css_vars") { |
| 352 | <...> |
| 353 | |
| 354 | # The output generated by the action. Not every file listed here is intended to be included |
| 355 | # in the GRD bundle. For example, we don't want our TypeScript configuration files to be included. |
| 356 | outputs = [ |
| 357 | "$target_gen_dir/Images.js", |
| 358 | "$target_gen_dir/$target_name-tsconfig.json", |
| 359 | ] |
| 360 | |
| 361 | data = [ "$target_gen_dir/Images.js" ] |
| 362 | |
| 363 | # Any `metadata` block can define a variable named `grd_files` which takes an `array` of |
| 364 | # generated files. The location should be in the `gen/` directory, not the original source |
| 365 | # location. |
| 366 | # |
| 367 | # In this case, we are listing both the generated `Images.js` file, as well as all image |
| 368 | # files that DevTools has. |
| 369 | metadata = { |
| 370 | grd_files = data |
| 371 | foreach(_image_file, devtools_image_files) { |
| 372 | grd_files += [ "$target_gen_dir/$_image_file" ] |
| 373 | } |
| 374 | } |
| 375 | } |
| 376 | ``` |
| 377 | |
| 378 | The GN metadata is traversed in `/BUILD.gn` and is written to a JSON file in `:input_grd_files`. |
| 379 | The JSON file is compared to the contents of `:expected_grd_files` which takes the `grd_files_release_sources` and `grd_files_debug_sources` (only in Debug mode) files as input. |
| 380 | |
| 381 | If the collected input files matches the expected listed GRD files, the GRD files is written to a GRD file by `:generate_devtools_grd`. |
| 382 | The GRD file is placed in `gen/third_party/devtools-frontend/src/front_end/` in Chromium. |
| 383 | |
Tim van der Lippe | 20e5ff2 | 2021-04-06 15:15:02 | [diff] [blame] | 384 | <!-- Links --> |
| 385 | |
| 386 | <!-- Design principles --> |
| 387 | [Web Platform Design Principles]: https://w3ctag.github.io/design-principles/ |
| 388 | [Put user needs first]: https://w3ctag.github.io/design-principles/#priority-of-constituencies |
| 389 | [Prefer simple solutions]: https://w3ctag.github.io/design-principles/#simplicity |
| 390 | [Load only what is necessary]: #load-only-what-is-necessary |
| 391 | [Prefer web platform features whenever possible]: #prefer-web-platform-features-whenever-possible |
| 392 | [Design with continuous deployment in mind]: #design-with-continuous-deployment-in-mind |
| 393 | |
| 394 | <!-- Web standards --> |
| 395 | [dynamic imports]: https://v8.dev/features/dynamic-import |
| 396 | [JavaScript modules]: https://v8.dev/features/modules |
| 397 | |
| 398 | <!-- Third-party packages --> |
| 399 | [Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/ |
| 400 | [ESLint]: https://eslint.org/ |
Paul Lewis | 2906222 | 2021-07-07 08:56:50 | [diff] [blame] | 401 | [GN]: https://gn.googlesource.com/gn/+/main/ |
| 402 | [GN `visibility` rules]: https://gn.googlesource.com/gn/+/main/docs/reference.md#var_visibility |
| 403 | [GN `metadata` definitions]: https://gn.googlesource.com/gn/+/main/docs/reference.md#var_metadata |
Tim van der Lippe | 20e5ff2 | 2021-04-06 15:15:02 | [diff] [blame] | 404 | [GRIT]: https://www.chromium.org/developers/tools-we-use-in-chromium/grit/grit-users-guide |
Tim van der Lippe | 20e5ff2 | 2021-04-06 15:15:02 | [diff] [blame] | 405 | [Ninja]: https://ninja-build.org/ |
| 406 | [TypeScript]: https://www.typescriptlang.org/ |