From 2ef31422df437032c5a8a348d14515931baacda5 Mon Sep 17 00:00:00 2001 From: Keen Yee Liau Date: Tue, 23 Feb 2021 10:51:23 -0800 Subject: [PATCH] fix(language-service): use single entry point for Ivy and View Engine Currently there are two entry points for the `@angular/language-service` package: - `@angular/language-service` This default entry point is for View Engine LS. Through the redirection of `main` field in `package.json`, it resolves to `./bundles/language-service.js`. - `@angular/language-service/bundles/ivy.js` This secondary entry point is for Ivy LS. TypeScript recently changed the behavior of tsserver to allow only package names as plugin names [1] for security reasons. This means the secondary entry point for Ivy LS can no longer be used. We implemented a quick hack in the module resolver (in the extension repo) to fix this, but the long term fix should be in `@angular/language-service`. Here, the `main` field in `package.json` is changed to `index.js`, and in the index file we conditionally load View Engine or Ivy based on the input config. This eliminates the need for multiple entry points. As part of this PR, I also removed all source code for View Engine and Ivy included in the NPM package. Consumers of this package should run the bundled output and nothing else. This would help us prevent an accidental import that results in execution of unbundled code. [1]: https://github.com/microsoft/TypeScript/pull/42713 --- packages/language-service/BUILD.bazel | 7 ++-- packages/language-service/api.ts | 13 +++++++ packages/language-service/bundles/BUILD.bazel | 2 +- packages/language-service/index.ts | 36 ++++++++++++++++++- packages/language-service/package.json | 2 +- 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/packages/language-service/BUILD.bazel b/packages/language-service/BUILD.bazel index 4a741ba60c29d8..3379d4e4f7f883 100644 --- a/packages/language-service/BUILD.bazel +++ b/packages/language-service/BUILD.bazel @@ -6,15 +6,17 @@ ts_library( name = "api", srcs = [ "api.ts", + "index.ts", ], deps = [ + "@npm//@types/node", "@npm//typescript", ], ) ts_library( name = "language-service", - srcs = ["index.ts"] + glob( + srcs = glob( [ "src/**/*.ts", ], @@ -54,8 +56,7 @@ pkg_npm( "//integration:__pkg__", ], deps = [ - ":language-service", - "//packages/language-service/ivy", + ":api", # min bundle is not used at the moment; omit from package to speed up build "//packages/language-service/bundles:language-service.js", "//packages/language-service/bundles:ivy.js", diff --git a/packages/language-service/api.ts b/packages/language-service/api.ts index 603f82dbb5d0ec..a78f3173e450e2 100644 --- a/packages/language-service/api.ts +++ b/packages/language-service/api.ts @@ -14,6 +14,19 @@ import * as ts from 'typescript'; +export interface NgLanguageServiceConfig { + /** + * If true, return only Angular results. Otherwise, return Angular + TypeScript + * results. + */ + angularOnly: boolean; + /** + * If true, return factory function for Ivy LS during plugin initialization. + * Otherwise return factory function for View Engine LS. + */ + ivy: boolean; +} + export type GetTcbResponse = { /** * The filename of the SourceFile this typecheck block belongs to. diff --git a/packages/language-service/bundles/BUILD.bazel b/packages/language-service/bundles/BUILD.bazel index d1c5915e1744d8..08e47e0d3833ed 100644 --- a/packages/language-service/bundles/BUILD.bazel +++ b/packages/language-service/bundles/BUILD.bazel @@ -3,7 +3,7 @@ load("//dev-infra/benchmark/ng_rollup_bundle:ng_rollup_bundle.bzl", "ng_rollup_b ng_rollup_bundle( name = "language-service", build_optimizer = False, - entry_point = "//packages/language-service:index.ts", + entry_point = "//packages/language-service:src/ts_plugin.ts", format = "amd", globals = { "fs": "fs", diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts index 0ed58aaa34a2d0..636dfc17611c2a 100644 --- a/packages/language-service/index.ts +++ b/packages/language-service/index.ts @@ -6,5 +6,39 @@ * found in the LICENSE file at https://angular.io/license */ +import * as ts from 'typescript/lib/tsserverlibrary'; +import {NgLanguageService, NgLanguageServiceConfig} from './api'; + export * from './api'; -export {create, getExternalFiles} from './src/ts_plugin'; + +interface PluginModule extends ts.server.PluginModule { + create(createInfo: ts.server.PluginCreateInfo): NgLanguageService; + onConfigurationChanged?(config: NgLanguageServiceConfig): void; +} + +const factory: ts.server.PluginModuleFactory = function(mod): PluginModule { + let plugin: PluginModule; + + function create(info: ts.server.PluginCreateInfo): NgLanguageService { + const config: NgLanguageServiceConfig = info.config; + const bundleName = config.ivy ? 'ivy.js' : 'language-service.js'; + plugin = require(`./bundles/${bundleName}`)(mod); + return plugin.create(info); + } + + function getExternalFiles(proj: ts.server.Project): string[] { + return plugin?.getExternalFiles?.(proj) ?? []; + } + + function onConfigurationChanged(config: NgLanguageServiceConfig): void { + plugin?.onConfigurationChanged?.(config); + } + + return { + create, + getExternalFiles, + onConfigurationChanged, + }; +}; + +module.exports = factory; diff --git a/packages/language-service/package.json b/packages/language-service/package.json index e3e5b2a1b56c2f..6bb933885dae8d 100644 --- a/packages/language-service/package.json +++ b/packages/language-service/package.json @@ -2,7 +2,7 @@ "name": "@angular/language-service", "version": "0.0.0-PLACEHOLDER", "description": "Angular - language services", - "main": "./bundles/language-service.js", + "main": "./index.js", "typings": "./index.d.ts", "author": "angular", "license": "MIT",