diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 05c672855d..8550dfcd76 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,36 +1,46 @@ { - "version": "0.1.0", + "version": "2.0.0", "problemMatcher": "$tsc-watch", "tasks": [ { - "taskName": "Bootstrap", + "label": "Bootstrap", "command": "npm", - "isShellCommand": true, - "showOutput": "silent", + "type": "shell", + "presentation": { "focus": false, "panel": "shared" }, "args": ["run", "bootstrap"], "isBackground": false }, { - "taskName": "Clean", + "label": "Clean", "command": "npm", - "isShellCommand": true, - "showOutput": "silent", + "type": "shell", + "presentation": { + "focus": false, + "panel": "shared" + }, "args": ["run", "clean"], "isBackground": false }, { - "isBuildCommand": true, - "taskName": "Compile", + "label": "Compile", + "group": { + "kind": "build", + "isDefault": true + }, "command": "npm", - "isShellCommand": true, - "showOutput": "silent", + "type": "shell", + "presentation": { + "focus": false, + "panel": "dedicated" + }, "args": ["run", "compile"], "isBackground": false, "problemMatcher": { "owner": "typescript", "fileLocation": "relative", "pattern": { - "regexp": "^(@salesforce\\/)(.*)\\((\\d+)\\,(\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", + "regexp": + "^(@salesforce\\/)(.*)\\((\\d+)\\,(\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", "file": 2, "line": 3, "severity": 5, @@ -40,29 +50,68 @@ } }, { - "taskName": "Lint", + "label": "Lint", "command": "npm", - "isShellCommand": true, - "showOutput": "silent", + "type": "shell", + "presentation": { + "focus": false, + "panel": "dedicated" + }, "args": ["run", "lint"], "isBackground": false }, { - "isTestCommand": true, - "taskName": "Test", + "label": "Watch", "command": "npm", - "isShellCommand": true, - "showOutput": "silent", - "args": ["run", "test"], - "isBackground": false + "type": "shell", + "presentation": { + "reveal": "silent", + "focus": false, + "panel": "dedicated" + }, + "args": ["run", "watch"], + "isBackground": true, + "problemMatcher": "$tsc-watch" }, { - "taskName": "Watch", + "label": "Test salesforcedx-webview-ui", "command": "npm", - "isShellCommand": true, - "showOutput": "silent", - "args": ["run", "watch"], + "type": "shell", + "isBackground": true, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "dedicated" + }, + "args": ["run", "test-webview"], + "problemMatcher": "$tsc-watch" + }, + { + "label": "Start salesforcedx-webview-ui server", + "command": "npm", + "type": "shell", "isBackground": true, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "dedicated" + }, + "args": ["run", "start-webview"], + "problemMatcher": "$tsc-watch" + }, + { + "label": "Bundle salesforcedx-webview-ui artifacts", + "command": "npm", + "type": "shell", + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "dedicated" + }, + "args": ["run", "bundle-webview"], "problemMatcher": "$tsc-watch" } ] diff --git a/docs/developing.md b/docs/developing.md index 42fe071961..2cbf7f4f10 100644 --- a/docs/developing.md +++ b/docs/developing.md @@ -49,7 +49,9 @@ You would only do this once after you cloned the repository. 1. We develop on the `develop` branch and release from the `master` branch. At this point, you should do initiate a `git checkout -t origin/develop` unless you are working on releasing. -1. `npm install` to bring in all the top-level dependencies +1. `npm install` to bring in all the top-level dependencies. Because of the + `postinstall` script, this also runs `npm run bootstrap` for you + automatically the first time. 1. Open the project in VS Code. You would usually do the following each time you close/reopen VS Code: @@ -61,10 +63,23 @@ You would usually do the following each time you close/reopen VS Code: (Ctrl+Shift+B or Cmd+Shift+B on Mac). The errors will show in the Problems panel. There is a known issue with the mapping so clicking on the error won't open the file. -1. In VS Code, open the debug view (Ctrl+Shift+D or Cmd+Shift+D on Mac) and from - the launch configuration dropdown, pick "Launch Extensions". -1. In VS Code, open the debug view (Ctrl+Shift+D or Cmd+Shift+D on Mac) and from - the launch configuration dropdown, pick "Launch * Tests". +1. If you are manipulating the webviews in salesforcedx-webviews-ui, you will + also invoke the Command Palette. The type in "task " (there is a space after) + and from the list of tasks. Choose from the following. + * Select "Start salesforcedx-webview-ui artifacts" to start an interactive + watcher to serve up webviews in your browser. This is the `start` script + from create-react-app and serves the same purpose. + * Select "Bundle salesforcedx-webview-ui artifacts" to copy the artifacts + over into the extensions so that they are optimized for our use. +1. In VS Code, you can invoke Command Palette. Then type in "debug " (there is + space after) and from the launch configuration dropdown, pick "Launch + Extensions". This launch extension will actually do a build for you as well. +1. In VS Code, you can invoke Command Palette. Then type in "debug " (there is + space after) and from the launch configuration dropdown, pick "Launch + Extensions without compile" if you had already build locally before. +1. In VS Code, you can invoke Command Palette. Then type in "debug " (there is + space after) and from the launch configuration dropdown, pick any of "Launch + * Tests". For more information, consult the VS Code [doc](https://code.visualstudio.com/docs/extensions/debugging-extensions) on how @@ -145,6 +160,16 @@ this command. This runs `npm run compile` on each of the package in packages. +### `npm run start-webview` + +This starts a local server that allows you to interactively work on the UI. This +is similar to the `start` command from create-react-app. + +### `npm run bundle-webview` + +This prepares (optimizes and minimizes) the webviews for inclusion into +salesforcedx-vscode-core. + ### `npm run clean` This run `npm run clean` on each of the package in packages. diff --git a/package.json b/package.json index 55d8125256..40e5e5f985 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,12 @@ }, "scripts": { "postinstall": - "lerna bootstrap -- --no-package-lock && node scripts/reformat-with-prettier", + "lerna bootstrap -- --no-package-lock && npm run bundle-webview && node scripts/reformat-with-prettier", "bootstrap": - "lerna bootstrap -- --no-package-lock && node scripts/reformat-with-prettier", + "lerna bootstrap -- --no-package-lock && npm run bundle-webview && node scripts/reformat-with-prettier", "clean": "lerna run clean", - "compile": "lerna run --stream compile", + "compile": + "lerna run --stream --ignore @salesforce/salesforcedx-webview-ui compile", "lint": "lerna run lint", "publish": "node scripts/publish.js", "prepush": "npm run lint", @@ -40,7 +41,12 @@ "watch": "lerna run --parallel watch", "eslint-check": "eslint --print-config .eslintrc.json | eslint-config-prettier-check", - "reformat": "node scripts/reformat-with-prettier.js" + "reformat": "node scripts/reformat-with-prettier.js", + "start-webview": "npm run start --prefix=packages/salesforcedx-webview-ui", + "build-webview": "npm run build --prefix=packages/salesforcedx-webview-ui", + "test-webview": "npm run test --prefix=packages/salesforcedx-webview-ui", + "bundle-webview": + "npm run compile --prefix=packages/salesforcedx-webview-ui" }, "repository": { "type": "git", diff --git a/packages/salesforcedx-vscode-core/.gitignore b/packages/salesforcedx-vscode-core/.gitignore new file mode 100644 index 0000000000..06b362db1d --- /dev/null +++ b/packages/salesforcedx-vscode-core/.gitignore @@ -0,0 +1 @@ +webviews/ \ No newline at end of file diff --git a/packages/salesforcedx-vscode-core/package.json b/packages/salesforcedx-vscode-core/package.json index 55b1675c2d..29aa413b9b 100644 --- a/packages/salesforcedx-vscode-core/package.json +++ b/packages/salesforcedx-vscode-core/package.json @@ -288,6 +288,11 @@ { "command": "sfdx.force.apex.log.get", "when": "sfdx:project_opened" + }, + { + "command": "sfdx.force.manifest.editor.show", + "when": + "sfdx:project_opened && config.salesforcedx-vscode-core.show_experimental_webviews" } ] }, @@ -451,6 +456,10 @@ { "command": "sfdx.force.apex.log.get", "title": "%force_apex_log_get_text%" + }, + { + "command": "sfdx.force.manifest.editor.show", + "title": "%force_manifest_editor_show_text%" } ], "configuration": { @@ -461,6 +470,11 @@ "type": ["boolean"], "default": true, "description": "%show_cli_success_msg_description%" + }, + "salesforcedx-vscode-core.show_experimental_webviews": { + "type": ["boolean"], + "default": false, + "description": "%show_experimental_webviews_description%" } } } diff --git a/packages/salesforcedx-vscode-core/package.nls.json b/packages/salesforcedx-vscode-core/package.nls.json index 53d614943f..d29d5ef07a 100644 --- a/packages/salesforcedx-vscode-core/package.nls.json +++ b/packages/salesforcedx-vscode-core/package.nls.json @@ -61,5 +61,10 @@ "isv_bootstrap_command_text": "SFDX: Create and Set Up Project for ISV Debugging", - "force_apex_log_get_text": "SFDX: Get Apex Debug Logs..." + "force_apex_log_get_text": "SFDX: Get Apex Debug Logs...", + + "force_manifest_editor_show_text": + "SFDX: Show Package Manifest Editor (Experimental)", + "show_experimental_webviews_description": + "Specifies whether the commands for the experimental webviews are available to the user." } diff --git a/packages/salesforcedx-vscode-core/src/index.ts b/packages/salesforcedx-vscode-core/src/index.ts index 1e931a8e53..c7a4c7ac97 100644 --- a/packages/salesforcedx-vscode-core/src/index.ts +++ b/packages/salesforcedx-vscode-core/src/index.ts @@ -64,8 +64,11 @@ import { isDemoMode } from './modes/demo-mode'; import { notificationService } from './notifications'; import { CANCEL_EXECUTION_COMMAND, cancelCommandExecution } from './statuses'; import { CancellableStatusBar, taskViewService } from './statuses'; +import { ManifestEditor } from './webviewPanels/manifestEditor'; -function registerCommands(): vscode.Disposable { +function registerCommands( + extensionContext: vscode.ExtensionContext +): vscode.Disposable { // Customer-facing commands const forceAuthWebLoginCmd = vscode.commands.registerCommand( 'sfdx.force.auth.web.login', @@ -252,6 +255,11 @@ function registerCommands(): vscode.Disposable { forceApexLogGet ); + const showManifestEditorCmd = vscode.commands.registerCommand( + 'sfdx.force.manifest.editor.show', + () => ManifestEditor.createOrShow(extensionContext) + ); + // Internal commands const internalCancelCommandExecution = vscode.commands.registerCommand( CANCEL_EXECUTION_COMMAND, @@ -301,6 +309,7 @@ function registerCommands(): vscode.Disposable { forceStopApexDebugLoggingCmd, isvDebugBootstrapCmd, forceApexLogGetCmd, + showManifestEditorCmd, internalCancelCommandExecution ); } @@ -372,7 +381,7 @@ export async function activate(context: vscode.ExtensionContext) { } // Commands - const commands = registerCommands(); + const commands = registerCommands(context); context.subscriptions.push(commands); // Task View diff --git a/packages/salesforcedx-vscode-core/src/messages/i18n.ts b/packages/salesforcedx-vscode-core/src/messages/i18n.ts index aee4f0aae3..9413c5c7a6 100644 --- a/packages/salesforcedx-vscode-core/src/messages/i18n.ts +++ b/packages/salesforcedx-vscode-core/src/messages/i18n.ts @@ -170,6 +170,7 @@ export const messages = { 'You are running Salesforce Extensions for VS Code in demo mode. You will be prompted for confirmation when connecting to production orgs.', demo_mode_prompt: 'Authorizing a business or production org is not recommended on a demo or shared machine. If you continue with the authentication, be sure to run "SFDX: Log Out from All Authorized Orgs" when you\'re done using this org.', + force_auth_logout_all_text: 'SFDX: Log Out from All Authorized Orgs', - force_auth_logout_all_text: 'SFDX: Log Out from All Authorized Orgs' + manifest_editor_title_message: 'Manifest Editor' }; diff --git a/packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts b/packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts new file mode 100644 index 0000000000..ad0a0631e2 --- /dev/null +++ b/packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { nls } from '../../src/messages'; + +const PUBLIC_URL_PLACEHOLDER = '__salesforcedx-vscode-core-prefix__'; + +export class ManifestEditor { + private static readonly viewTitle = nls.localize( + 'manifest_editor_title_message' + ); + private static readonly viewType = 'manifestEditor.type'; + private static currentPanel: ManifestEditor | undefined; + private readonly panel: vscode.WebviewPanel; + private readonly extensionContext: vscode.ExtensionContext; + private disposables: vscode.Disposable[] = []; + + public static createOrShow(extensionContext: vscode.ExtensionContext) { + const column = vscode.window.activeTextEditor + ? vscode.window.activeTextEditor.viewColumn + : undefined; + + if (ManifestEditor.currentPanel) { + ManifestEditor.currentPanel.panel.reveal(column); + } else { + ManifestEditor.currentPanel = new ManifestEditor( + extensionContext, + column || vscode.ViewColumn.One + ); + } + } + + private constructor( + extensionContext: vscode.ExtensionContext, + column: vscode.ViewColumn + ) { + this.extensionContext = extensionContext; + + this.panel = vscode.window.createWebviewPanel( + ManifestEditor.viewType, + ManifestEditor.viewTitle, + column, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [ + vscode.Uri.file( + path.join(this.extensionContext.extensionPath, 'webviews') + ) + ] + } + ); + + this.panel.onDidDispose(() => this.dispose(), null, this.disposables); + + this.panel.onDidChangeViewState( + event => { + if (this.panel.visible) { + void this.update(); + } + }, + null, + this.disposables + ); + + void this.show(); + } + + public dispose() { + ManifestEditor.currentPanel = undefined; + + this.panel.dispose(); + + while (this.disposables.length) { + const disposable = this.disposables.pop(); + if (disposable) { + disposable.dispose(); + } + } + } + + private async update() { + void this.show(); + } + + private async show() { + this.panel.webview.html = await this.getNormalizedResourceContents( + 'webviews/ManifestEditor/index.html' + ); + } + + private async getNormalizedResourceContents( + resourcePath: string + ): Promise { + const contents = await this.getResourceContents(resourcePath); + const normalized = contents.replace( + new RegExp(`${PUBLIC_URL_PLACEHOLDER}`, 'g'), + vscode.Uri + .file(this.extensionContext.asAbsolutePath('.')) + .with({ + scheme: 'vscode-resource' + }) + .toString() + ); + return normalized; + } + + private async getResourceContents(resourcePath: string): Promise { + return new Promise((resolve, reject) => { + fs.readFile( + this.extensionContext.asAbsolutePath(resourcePath), + 'utf8', + (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + } + ); + }); + } +} diff --git a/packages/salesforcedx-vscode-lightning/package.json b/packages/salesforcedx-vscode-lightning/package.json index 4db303f736..afc4880100 100644 --- a/packages/salesforcedx-vscode-lightning/package.json +++ b/packages/salesforcedx-vscode-lightning/package.json @@ -20,7 +20,7 @@ "engines": { "vscode": "^1.23.0" }, - "categories": ["Languages"], + "categories": ["Programming Languages"], "dependencies": { "@salesforce/salesforcedx-slds-linter": "42.18.0" }, diff --git a/packages/salesforcedx-vscode-lwc-next/package.json b/packages/salesforcedx-vscode-lwc-next/package.json index c3d0b2245c..27973aa1a9 100644 --- a/packages/salesforcedx-vscode-lwc-next/package.json +++ b/packages/salesforcedx-vscode-lwc-next/package.json @@ -21,7 +21,7 @@ "engines": { "vscode": "^1.23.0" }, - "categories": ["Languages"], + "categories": ["Programming Languages"], "dependencies": { "@salesforce/salesforcedx-utils-vscode": "42.18.0", "ajv": "^6.1.1", diff --git a/packages/salesforcedx-vscode-lwc/package.json b/packages/salesforcedx-vscode-lwc/package.json index 3048251c6b..34e7b11b68 100644 --- a/packages/salesforcedx-vscode-lwc/package.json +++ b/packages/salesforcedx-vscode-lwc/package.json @@ -21,7 +21,7 @@ "engines": { "vscode": "^1.23.0" }, - "categories": ["Languages"], + "categories": ["Programming Languages"], "dependencies": { "@salesforce/salesforcedx-utils-vscode": "42.18.0", "ajv": "^6.1.1", diff --git a/packages/salesforcedx-webview-ui/.gitignore b/packages/salesforcedx-webview-ui/.gitignore new file mode 100644 index 0000000000..d30f40ef44 --- /dev/null +++ b/packages/salesforcedx-webview-ui/.gitignore @@ -0,0 +1,21 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# dependencies +/node_modules + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/packages/salesforcedx-webview-ui/README.md b/packages/salesforcedx-webview-ui/README.md new file mode 100644 index 0000000000..32fc449a79 --- /dev/null +++ b/packages/salesforcedx-webview-ui/README.md @@ -0,0 +1,187 @@ +# Introduction + +This package contains React web components that we use in our Webview panels for +VS Code. + +In general, VS Code doesn't support arbitrary manipulations to its UI via the +DOM. This is documented at its [Extensibility Principles and +Patterns](https://code.visualstudio.com/docs/extensionAPI/patterns-and-principles#_extensibility-api) +page. + +However, there are times when we do need to provide our own custom UI within VS +Code. Fortunately, VS Code 1.23 and above provide for this via the [VS Code +WebView API](https://code.visualstudio.com/docs/extensions/webview) + +The gist of this approach is that we create HTML pages that contain React +components, and we expose them as panels inside VS Code. + +# Architecture + +``` ++----------------------------------------------------+ +| | +| +-------------------------------------------+ | +| | | | +| | +------------------+ +------------------+ | | +| | | | | | | | +| | | Component #A | | Component #B | | | +| | | | | | | | +| | | | | | | | +| | +------------------+ +------------------+ | | +| | +---------------------------------------+ | | +| | | | | | +| | | Component #E | | | +| | | | | | +| | | | | | +| | +---------------------------------------+ | | +| | | | +| | | | +| | | | +-------------------------+ +| | +-----------------+ | | +-----------+ | | +| | | | | | |postMessage| | +-----------------+ | +| | | | | --+------+-----------+-------+> | | | +| | | +-------------+ | | | | | | | +| | | |EventListener| | | | | | +-------------+ | | +| | | +-------------+ | | | | | |EventListener| | | +| | | | | | | | +-------------+ | | +| | | | | <-+------+-----------+-------+- | | | +| | <----------+ | | |postMessage| | | | | +| | | | +-----------+ | <----------+ | +| +-------------------------------------------+ | | | +| HTML Page #1 | | | +| | | | ++----------------------------------------------------+ | | + WebviewPanel #1 | | + | | - ++----------------------------------------------------+ | | +| | | | +| +-------------------------------------------+ | | | +----------------+ +| | | | | <+-----> F/S | +| | +------------------+ +------------------+ | | | | +----------------+ +| | | | | | | | | | +----------------+ +| | | Component #B | | Component #A | | | | <+-----> Web | +| | | | | | | | | | +----------------+ +| | | | | | | | | | +----------------+ +| | +------------------+ +------------------+ | | | <+-----> Salesforce CLI | +| | +---------------------------------------+ | | | | +----------------+ +| | | | | | | | +| | | Component #C | | | | | +| | | | | | | | +| | | | | | | | +| | +---------------------------------------+ | | | | +| | | | | | +| | | | | | +| | | | | | +| | +-----------------+ | | | +-----------------+ | +| | | | | | +-----------+ | | | | +| | | | | | |postMessage| | | | | +| | | +-------------+ | | --+------+-----------+-------+> | +-------------+ | | +| | | |EventListener| | | | | | |EventListener| | | +| | | +-------------+ | | | | | +-------------+ | | +| | | | | | | | | | +| | | | | | | | | | +| | <----------+ | <-+------+-----------+-------+- <----------+ | +| | | | |postMessage| | | +| +-------------------------------------------+ | +-----------+ | | +| HTML Page #2 | +-------------------------+ +| | ++----------------------------------------------------+ VS Code Extension + WebviewPanel #2 + +Created with Monodraw +``` + +We can embed an HTML page that contains our components into a WebviewPanel. +Those components can communicate with the VS Code Extension via message passing. +Similarly, the VS Code Extension can communicate with the WebviewPanel via +message passing. The communication protocol is up to the implementor. + +There is no limit on the number of WebviewPanels that we can embed. However, +since each one WebviewPanel is backed by an Electron WebView, which runs in its +own process, there could be performance penalties. + +# Third-party Libraries + +* [react-script-ts](https://github.com/wmonk/create-react-app-typescript) - Provides the template for the project structure +* [create-react-app](https://github.com/facebook/create-react-app) - The basis for the project structure. Read this to understand how things work underneath. +* [Blueprint](http://blueprintjs.com/docs/v2/) - For UI components +* [Jest](https://facebook.github.io/jest/) - For testing + +# Useful Reading + +* [`` Tag](https://electronjs.org/docs/api/webview-tag) +* [Interop’s Labyrinth: Sharing Code Between Web & Electron Apps](https://slack.engineering/interops-labyrinth-sharing-code-between-web-electron-apps-f9474d62eccc) +* [Growing Pains: Migrating Slack’s Desktop App to BrowserView](https://slack.engineering/growing-pains-migrating-slacks-desktop-app-to-browserview-2759690d9c7b) + +# Developer Flow + +## Rapid Iteration of the UI + +We need a way to rapidly iterate on the UI while we are prototyping. We +accomplish this via an in-memory web server that watches for changes to files +(on saves). + +1. Invoke the Command Paletter in VS Code. +1. Type "task " (there is a space after task). +1. Select "Start salesforcedx-webview-ui" +1. This should open your default web browser to the main page. +1. Navigate to an entry point of your choice. + +As you make edits, the web page should also refresh automatically. If there are +errors, you can see them in the Output panel in VS Code. + +### Debugging + +You can use the Chrome Developer with [React Developer +Tools](https://github.com/facebook/react-devtools) to debug. The in-memory web +server automatically includes the necessary source maps. + +## Bundling the UI into VS Code + +Once we are satisfied with our UI, we can bundle them into our VS Code +Extension. The bundled version has been minimized so that it loads more quickly. + +1. Invoke the Command Palette in VS Code. +1. Type "task " (there is a space after task). +1. Select "Bundle salesforcedx-webview-ui" +1. This creates an optimized and minified build of your entry points and copies + it into packages/salseforcedx-vscode-core/webviews. + +Anytime you make changes and are ready to test them out in VS Code, be sure to +**repeat** those steps. We don't do this automatically since producing an +optimized and minimized build takes \~15 seconds and we don't want to do this +repeatedly on each save. + + +### Debugging + +_This needs to be improved, the current debugging is quite difficult since we +ship minimized resources to optimize for load times_. + +## Entry points + +The convention is that each WebviewPanel will contain one HTML page. We call +this an entry point. Entry points are declared as follows: + +1. Create a new folder in src/entries. +1. By convention, use a similar name to the name of the panel that you would expose in VS Code. +1. Add the necessary files to the new folder. +1. Be sure to add a new entry to config/paths.js + +```javascript +/////////////////////////////////////// +// Add the different entry points here +/////////////////////////////////////// +const entries = ['ManifestEditor']; +``` + +## Testing the React Components + +1. Invoke the Command Palette in VS Code. +1. Type "task " (there is a space after task). +1. Select "Test salesforcedx-webview-ui" + +Tests should follow the convention of `some_name.test.ts`. This prevents the +tests from being bundled in the final optimized and minimized output. + +For more information, see [create-react-app/Filename Conventions](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#filename-conventions) \ No newline at end of file diff --git a/packages/salesforcedx-webview-ui/config/env.js b/packages/salesforcedx-webview-ui/config/env.js new file mode 100644 index 0000000000..7a31c25cb2 --- /dev/null +++ b/packages/salesforcedx-webview-ui/config/env.js @@ -0,0 +1,96 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const paths = require('./paths'); + +// Make sure that including paths.js after env.js will read .env variables. +delete require.cache[require.resolve('./paths')]; + +const NODE_ENV = process.env.NODE_ENV; +if (!NODE_ENV) { + throw new Error( + 'The NODE_ENV environment variable is required but was not specified.' + ); +} + +// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use +var dotenvFiles = [ + `${paths.dotenv}.${NODE_ENV}.local`, + `${paths.dotenv}.${NODE_ENV}`, + // Don't include `.env.local` for `test` environment + // since normally you expect tests to produce the same + // results for everyone + NODE_ENV !== 'test' && `${paths.dotenv}.local`, + paths.dotenv, +].filter(Boolean); + +// Load environment variables from .env* files. Suppress warnings using silent +// if this file is missing. dotenv will never modify any environment variables +// that have already been set. Variable expansion is supported in .env files. +// https://github.com/motdotla/dotenv +// https://github.com/motdotla/dotenv-expand +dotenvFiles.forEach(dotenvFile => { + if (fs.existsSync(dotenvFile)) { + require('dotenv-expand')( + require('dotenv').config({ + path: dotenvFile, + }) + ); + } +}); + +// We support resolving modules according to `NODE_PATH`. +// This lets you use absolute paths in imports inside large monorepos: +// https://github.com/facebookincubator/create-react-app/issues/253. +// It works similar to `NODE_PATH` in Node itself: +// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders +// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. +// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. +// https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 +// We also resolve them to make sure all tools using them work consistently. +const appDirectory = fs.realpathSync(process.cwd()); +process.env.NODE_PATH = (process.env.NODE_PATH || '') + .split(path.delimiter) + .filter(folder => folder && !path.isAbsolute(folder)) + .map(folder => path.resolve(appDirectory, folder)) + .join(path.delimiter); + +// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be +// injected into the application via DefinePlugin in Webpack configuration. +const REACT_APP = /^REACT_APP_/i; + +function getClientEnvironment(publicUrl) { + const raw = Object.keys(process.env) + .filter(key => REACT_APP.test(key)) + .reduce( + (env, key) => { + env[key] = process.env[key]; + return env; + }, + { + // Useful for determining whether we’re running in production mode. + // Most importantly, it switches React into the correct mode. + NODE_ENV: process.env.NODE_ENV || 'development', + // Useful for resolving the correct path to static assets in `public`. + // For example, . + // This should only be used as an escape hatch. Normally you would put + // images into the `src` and `import` them in code to get their paths. + PUBLIC_URL: publicUrl, + } + ); + // Stringify all values so we can feed into Webpack DefinePlugin + const stringified = { + 'process.env': Object.keys(raw).reduce( + (env, key) => { + env[key] = JSON.stringify(raw[key]); + return env; + }, + {} + ), + }; + + return { raw, stringified }; +} + +module.exports = getClientEnvironment; diff --git a/packages/salesforcedx-webview-ui/config/jest/cssTransform.js b/packages/salesforcedx-webview-ui/config/jest/cssTransform.js new file mode 100644 index 0000000000..8f65114812 --- /dev/null +++ b/packages/salesforcedx-webview-ui/config/jest/cssTransform.js @@ -0,0 +1,14 @@ +'use strict'; + +// This is a custom Jest transformer turning style imports into empty objects. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process() { + return 'module.exports = {};'; + }, + getCacheKey() { + // The output is always the same. + return 'cssTransform'; + }, +}; diff --git a/packages/salesforcedx-webview-ui/config/jest/fileTransform.js b/packages/salesforcedx-webview-ui/config/jest/fileTransform.js new file mode 100644 index 0000000000..9e4047d358 --- /dev/null +++ b/packages/salesforcedx-webview-ui/config/jest/fileTransform.js @@ -0,0 +1,12 @@ +'use strict'; + +const path = require('path'); + +// This is a custom Jest transformer turning file imports into filenames. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process(src, filename) { + return `module.exports = ${JSON.stringify(path.basename(filename))};`; + }, +}; diff --git a/packages/salesforcedx-webview-ui/config/jest/typescriptTransform.js b/packages/salesforcedx-webview-ui/config/jest/typescriptTransform.js new file mode 100644 index 0000000000..9b138ac8ec --- /dev/null +++ b/packages/salesforcedx-webview-ui/config/jest/typescriptTransform.js @@ -0,0 +1,7 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +'use strict'; + +const tsJestPreprocessor = require('ts-jest/preprocessor'); + +module.exports = tsJestPreprocessor; diff --git a/packages/salesforcedx-webview-ui/config/paths.js b/packages/salesforcedx-webview-ui/config/paths.js new file mode 100644 index 0000000000..8f8b945bb2 --- /dev/null +++ b/packages/salesforcedx-webview-ui/config/paths.js @@ -0,0 +1,86 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +// Make sure any symlinks in the project folder are resolved: +// https://github.com/facebookincubator/create-react-app/issues/637 +const appDirectory = fs.realpathSync(process.cwd()); +const resolveApp = relativePath => path.resolve(appDirectory, relativePath); + +const envPublicUrl = process.env.PUBLIC_URL; + +function ensureSlash(path, needsSlash) { + const hasSlash = path.endsWith('/'); + if (hasSlash && !needsSlash) { + return path.substr(path, path.length - 1); + } else if (!hasSlash && needsSlash) { + return `${path}/`; + } else { + return path; + } +} + +const getPublicUrl = appPackageJson => + envPublicUrl || require(appPackageJson).homepage; + +// We use `PUBLIC_URL` environment variable or "homepage" field to infer +// "public path" at which the app is served. +// Webpack needs to know it to put the right