From d8a93a89c79120cff3737c2787a5633384ad44fd Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Thu, 31 May 2018 15:48:50 -0700 Subject: [PATCH 01/17] Create a webview-ui package This is the package that will contain all the .html that we will use for each webview. @W-5013822@ --- .vscode/tasks.json | 11 +- package.json | 3 +- packages/salesforcedx-webview-ui/.gitignore | 21 ++ .../salesforcedx-webview-ui/config/env.js | 96 +++++ .../config/jest/cssTransform.js | 14 + .../config/jest/fileTransform.js | 12 + .../config/jest/typescriptTransform.js | 7 + .../salesforcedx-webview-ui/config/paths.js | 86 +++++ .../config/polyfills.js | 22 ++ .../config/webpack.config.dev.js | 266 ++++++++++++++ .../config/webpack.config.prod.js | 335 ++++++++++++++++++ .../config/webpackDevServer.config.js | 95 +++++ packages/salesforcedx-webview-ui/images.d.ts | 3 + packages/salesforcedx-webview-ui/package.json | 111 ++++++ .../salesforcedx-webview-ui/scripts/build.js | 144 ++++++++ .../salesforcedx-webview-ui/scripts/start.js | 102 ++++++ .../salesforcedx-webview-ui/scripts/test.js | 31 ++ .../src/entries/ManifestEditor/App.test.tsx | 9 + .../src/entries/ManifestEditor/App.tsx | 35 ++ .../src/entries/ManifestEditor/index.css | 5 + .../src/entries/ManifestEditor/index.tsx | 6 + .../src/entries/Sample/App.css | 28 ++ .../src/entries/Sample/App.test.tsx | 9 + .../src/entries/Sample/App.tsx | 23 ++ .../src/entries/Sample/index.css | 5 + .../src/entries/Sample/index.tsx | 6 + .../src/entries/Sample/logo.svg | 7 + .../templates/index.html | 11 + .../salesforcedx-webview-ui/tsconfig.json | 22 ++ .../tsconfig.prod.json | 3 + .../tsconfig.test.json | 6 + packages/salesforcedx-webview-ui/tslint.json | 10 + 32 files changed, 1542 insertions(+), 2 deletions(-) create mode 100644 packages/salesforcedx-webview-ui/.gitignore create mode 100644 packages/salesforcedx-webview-ui/config/env.js create mode 100644 packages/salesforcedx-webview-ui/config/jest/cssTransform.js create mode 100644 packages/salesforcedx-webview-ui/config/jest/fileTransform.js create mode 100644 packages/salesforcedx-webview-ui/config/jest/typescriptTransform.js create mode 100644 packages/salesforcedx-webview-ui/config/paths.js create mode 100644 packages/salesforcedx-webview-ui/config/polyfills.js create mode 100644 packages/salesforcedx-webview-ui/config/webpack.config.dev.js create mode 100644 packages/salesforcedx-webview-ui/config/webpack.config.prod.js create mode 100644 packages/salesforcedx-webview-ui/config/webpackDevServer.config.js create mode 100644 packages/salesforcedx-webview-ui/images.d.ts create mode 100644 packages/salesforcedx-webview-ui/package.json create mode 100644 packages/salesforcedx-webview-ui/scripts/build.js create mode 100644 packages/salesforcedx-webview-ui/scripts/start.js create mode 100644 packages/salesforcedx-webview-ui/scripts/test.js create mode 100644 packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.test.tsx create mode 100644 packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.tsx create mode 100644 packages/salesforcedx-webview-ui/src/entries/ManifestEditor/index.css create mode 100644 packages/salesforcedx-webview-ui/src/entries/ManifestEditor/index.tsx create mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/App.css create mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/App.test.tsx create mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/App.tsx create mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/index.css create mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/index.tsx create mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/logo.svg create mode 100644 packages/salesforcedx-webview-ui/templates/index.html create mode 100644 packages/salesforcedx-webview-ui/tsconfig.json create mode 100644 packages/salesforcedx-webview-ui/tsconfig.prod.json create mode 100644 packages/salesforcedx-webview-ui/tsconfig.test.json create mode 100644 packages/salesforcedx-webview-ui/tslint.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 05c672855d..c1657adce0 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -30,7 +30,8 @@ "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, @@ -64,6 +65,14 @@ "args": ["run", "watch"], "isBackground": true, "problemMatcher": "$tsc-watch" + }, + { + "taskName": "Start salesforcedx-webview-ui server", + "command": "npm", + "isBackground": true, + "isShellCommand": true, + "args": ["run", "start-webview"], + "problemMatcher": "$tsc-watch" } ] } diff --git a/package.json b/package.json index 55d8125256..5f10c6bff2 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "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 start --prefix=packages/salesforcedx-webview-ui" }, "repository": { "type": "git", 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/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, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />. + // 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..f99d8a32a1 --- /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 <script> hrefs into HTML even in +// single-page apps that may serve index.html for nested URLs like /todos/42. +// We can't use a relative path in HTML because we don't want to load something +// like /todos/42/static/js/bundle.7289d.js. We have to know the root. +function getServedPath(appPackageJson) { + const publicUrl = getPublicUrl(appPackageJson); + const servedUrl = + envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/'); + return ensureSlash(servedUrl, true); +} + +/////////////////////////////////////// +// Add the different entry points here +/////////////////////////////////////// +const entries = ['ManifestEditor', 'Sample']; + +// config after eject: we're in ./config/ +module.exports = { + entries, + dotenv: resolveApp('.env'), + appBuild: resolveApp('build'), + appTemplates: resolveApp('templates'), + templateIndexHtml: resolveApp('templates/index.html'), + appPackageJson: resolveApp('package.json'), + appSrc: resolveApp('src'), + yarnLockFile: resolveApp('yarn.lock'), + testsSetup: resolveApp('src/setupTests.ts'), + appNodeModules: resolveApp('node_modules'), + appTsConfig: resolveApp('tsconfig.json'), + appTsProdConfig: resolveApp('tsconfig.prod.json'), + appTsLint: resolveApp('tslint.json'), + publicUrl: getPublicUrl(resolveApp('package.json')), + servedPath: getServedPath(resolveApp('package.json')), + webpackEntriesDevFn: () => { + let values = new Map(); + entries.forEach( + entry => + (values[entry] = [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + resolveApp(`src/entries/${entry}/index.tsx`) + ]) + ); + return values; + }, + webpackEntriesProdFn: () => { + let values = new Map(); + entries.forEach( + entry => + (values[entry] = [ + require.resolve('./polyfills'), + resolveApp(`src/entries/${entry}/index.tsx`) + ]) + ); + return values; + } +}; diff --git a/packages/salesforcedx-webview-ui/config/polyfills.js b/packages/salesforcedx-webview-ui/config/polyfills.js new file mode 100644 index 0000000000..66dff0a8b1 --- /dev/null +++ b/packages/salesforcedx-webview-ui/config/polyfills.js @@ -0,0 +1,22 @@ +'use strict'; + +if (typeof Promise === 'undefined') { + // Rejection tracking prevents a common issue where React gets into an + // inconsistent state due to an error, but it gets swallowed by a Promise, + // and the user has no idea what causes React's erratic future behavior. + require('promise/lib/rejection-tracking').enable(); + window.Promise = require('promise/lib/es6-extensions.js'); +} + +// fetch() polyfill for making API calls. +require('whatwg-fetch'); + +// Object.assign() is commonly used with React. +// It will use the native implementation if it's present and isn't buggy. +Object.assign = require('object-assign'); + +// In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. +// We don't polyfill it in the browser--this is user's responsibility. +if (process.env.NODE_ENV === 'test') { + require('raf').polyfill(global); +} diff --git a/packages/salesforcedx-webview-ui/config/webpack.config.dev.js b/packages/salesforcedx-webview-ui/config/webpack.config.dev.js new file mode 100644 index 0000000000..7a8d42a3f1 --- /dev/null +++ b/packages/salesforcedx-webview-ui/config/webpack.config.dev.js @@ -0,0 +1,266 @@ +'use strict'; + +const autoprefixer = require('autoprefixer'); +const path = require('path'); +const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); +const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); +const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const getClientEnvironment = require('./env'); +const paths = require('./paths'); +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); + +// Webpack uses `publicPath` to determine where the app is being served from. +// In development, we always serve from the root. This makes config easier. +const publicPath = '/'; +// `publicUrl` is just like `publicPath`, but we will provide it to our app +// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. +// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz. +const publicUrl = ''; +// Get environment variables to inject into our app. +const env = getClientEnvironment(publicUrl); + +function generateIndexLocations() { + return paths.entries.map( + entry => + new HtmlWebpackPlugin({ + inject: true, + template: paths.templateIndexHtml, + chunks: [entry], + filename: `${entry}/index.html` + }) + ); +} + +// This is the development configuration. +// It is focused on developer experience and fast rebuilds. +// The production configuration is different and lives in a separate file. +module.exports = { + // You may want 'eval' instead if you prefer to see the compiled output in DevTools. + // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343. + devtool: 'cheap-module-source-map', + entry: paths.webpackEntriesDevFn , + output: { + // Add /* filename */ comments to generated require()s in the output. + pathinfo: true, + // This does not produce a real file. It's just the virtual path that is + // served by WebpackDevServer in development. This is the JS bundle + // containing code from all our entry points, and the Webpack runtime. + filename: '[name]/js/[name].js', + // There are also additional JS chunk files if you use code splitting. + chunkFilename: '[name]/js/[name].chunk.js', + // This is the URL that app is served from. We use "/" in development. + publicPath: publicPath, + // Point sourcemap entries to original disk location (format as URL on Windows) + devtoolModuleFilenameTemplate: info => + path.resolve(info.absoluteResourcePath).replace(/\\/g, '/') + }, + resolve: { + // This allows you to set a fallback for where Webpack should look for modules. + // We placed these paths second because we want `node_modules` to "win" + // if there are any conflicts. This matches Node resolution mechanism. + // https://github.com/facebookincubator/create-react-app/issues/253 + modules: ['node_modules', paths.appNodeModules].concat( + // It is guaranteed to exist because we tweak it in `env.js` + process.env.NODE_PATH.split(path.delimiter).filter(Boolean) + ), + // These are the reasonable defaults supported by the Node ecosystem. + // We also include JSX as a common component filename extension to support + // some tools, although we do not recommend using it, see: + // https://github.com/facebookincubator/create-react-app/issues/290 + // `web` extension prefixes have been added for better support + // for React Native Web. + extensions: [ + '.mjs', + '.web.ts', + '.ts', + '.web.tsx', + '.tsx', + '.web.js', + '.js', + '.json', + '.web.jsx', + '.jsx' + ], + alias: { + // Support React Native Web + // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ + 'react-native': 'react-native-web' + }, + plugins: [ + // Prevents users from importing files from outside of src/ (or node_modules/). + // This often causes confusion because we only process files within src/ with babel. + // To fix this, we prevent you from importing files out of src/ -- if you'd like to, + // please link the files into your node_modules/ and let module-resolution kick in. + // Make sure your source files are compiled, as they will not be processed in any way. + new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), + new TsconfigPathsPlugin({ configFile: paths.appTsConfig }) + ] + }, + module: { + strictExportPresence: true, + rules: [ + // TODO: Disable require.ensure as it's not a standard language feature. + // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. + // { parser: { requireEnsure: false } }, + + { + test: /\.(js|jsx|mjs)$/, + loader: require.resolve('source-map-loader'), + enforce: 'pre', + include: paths.appSrc + }, + { + // "oneOf" will traverse all following loaders until one will + // match the requirements. When no loader matches it will fall + // back to the "file" loader at the end of the loader list. + oneOf: [ + // "url" loader works like "file" loader except that it embeds assets + // smaller than specified limit in bytes as data URLs to avoid requests. + // A missing `test` is equivalent to a match. + { + test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], + loader: require.resolve('url-loader'), + options: { + limit: 10000, + name: 'media/[name].[ext]' + } + }, + { + test: /\.(js|jsx|mjs)$/, + include: paths.appSrc, + loader: require.resolve('babel-loader'), + options: { + compact: true + } + }, + + // Compile .tsx? + { + test: /\.(ts|tsx)$/, + include: paths.appSrc, + use: [ + { + loader: require.resolve('ts-loader'), + options: { + // disable type checker - we will use it in fork plugin + transpileOnly: true + } + } + ] + }, + // "postcss" loader applies autoprefixer to our CSS. + // "css" loader resolves paths in CSS and adds assets as dependencies. + // "style" loader turns CSS into JS modules that inject <style> tags. + // In production, we use a plugin to extract that CSS to a file, but + // in development "style" loader enables hot editing of CSS. + { + test: /\.css$/, + use: [ + require.resolve('style-loader'), + { + loader: require.resolve('css-loader'), + options: { + importLoaders: 1 + } + }, + { + loader: require.resolve('postcss-loader'), + options: { + // Necessary for external CSS imports to work + // https://github.com/facebookincubator/create-react-app/issues/2677 + ident: 'postcss', + plugins: () => [ + require('postcss-flexbugs-fixes'), + autoprefixer({ + browsers: [ + '>1%', + 'last 4 versions', + 'Firefox ESR', + 'not ie < 9' // React doesn't support IE8 anyway + ], + flexbox: 'no-2009' + }) + ] + } + } + ] + }, + // "file" loader makes sure those assets get served by WebpackDevServer. + // When you `import` an asset, you get its (virtual) filename. + // In production, they would get copied to the `build` folder. + // This loader doesn't use a "test" so it will catch all modules + // that fall through the other loaders. + { + // Exclude `js` files to keep "css" loader working as it injects + // its runtime that would otherwise processed through "file" loader. + // Also exclude `html` and `json` extensions so they get processed + // by webpacks internal loaders. + exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/], + loader: require.resolve('file-loader'), + options: { + name: 'media/[name].[ext]' + } + } + ] + } + // ** STOP ** Are you adding a new loader? + // Make sure to add the new loader(s) before the "file" loader. + ] + }, + plugins: [ + // Makes some environment variables available in index.html. + // The public URL is available as %PUBLIC_URL% in index.html, e.g.: + // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> + // In development, this will be an empty string. + new InterpolateHtmlPlugin(env.raw), + ...generateIndexLocations(), + // Add module names to factory functions so they appear in browser profiler. + new webpack.NamedModulesPlugin(), + // Makes some environment variables available to the JS code, for example: + // if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`. + new webpack.DefinePlugin(env.stringified), + // This is necessary to emit hot updates (currently CSS only): + new webpack.HotModuleReplacementPlugin(), + // Watcher doesn't work well if you mistype casing in a path so we use + // a plugin that prints an error when you attempt to do this. + // See https://github.com/facebookincubator/create-react-app/issues/240 + new CaseSensitivePathsPlugin(), + // If you require a missing module and then `npm install` it, you still have + // to restart the development server for Webpack to discover it. This plugin + // makes the discovery automatic so you don't have to restart. + // See https://github.com/facebookincubator/create-react-app/issues/186 + new WatchMissingNodeModulesPlugin(paths.appNodeModules), + // Moment.js is an extremely popular library that bundles large locale files + // by default due to how Webpack interprets its code. This is a practical + // solution that requires the user to opt into importing specific locales. + // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack + // You can remove this if you don't use Moment.js: + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + // Perform type checking and linting in a separate process to speed up compilation + new ForkTsCheckerWebpackPlugin({ + async: false, + watch: paths.appSrc, + tsconfig: paths.appTsConfig, + tslint: paths.appTsLint + }) + ], + // Some libraries import Node modules but don't use them in the browser. + // Tell Webpack to provide empty mocks for them so importing them works. + node: { + dgram: 'empty', + fs: 'empty', + net: 'empty', + tls: 'empty', + child_process: 'empty' + }, + // Turn off performance hints during development because we don't do any + // splitting or minification in interest of speed. These warnings become + // cumbersome. + performance: { + hints: false + } +}; diff --git a/packages/salesforcedx-webview-ui/config/webpack.config.prod.js b/packages/salesforcedx-webview-ui/config/webpack.config.prod.js new file mode 100644 index 0000000000..c5685768cf --- /dev/null +++ b/packages/salesforcedx-webview-ui/config/webpack.config.prod.js @@ -0,0 +1,335 @@ +'use strict'; + +const autoprefixer = require('autoprefixer'); +const path = require('path'); +const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); +const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const paths = require('./paths'); +const getClientEnvironment = require('./env'); +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); +const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); + +// Webpack uses `publicPath` to determine where the app is being served from. +// It requires a trailing slash, or the file assets will get an incorrect path. +const publicPath = paths.servedPath; +// Some apps do not use client-side routing with pushState. +// For these, "homepage" can be set to "." to enable relative asset paths. +const shouldUseRelativeAssetPaths = publicPath === './'; +// Source maps are resource heavy and can cause out of memory issue for large source files. +const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; +// `publicUrl` is just like `publicPath`, but we will provide it to our app +// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. +// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz. +const publicUrl = publicPath.slice(0, -1); +// Get environment variables to inject into our app. +const env = getClientEnvironment(publicUrl); + +// Assert this just to be safe. +// Development builds of React are slow and not intended for production. +if (env.stringified['process.env'].NODE_ENV !== '"production"') { + throw new Error('Production builds must have NODE_ENV=production.'); +} + +// Note: defined here because it will be used more than once. +const cssFilename = '[name]/css/[name].css'; + +// ExtractTextPlugin expects the build output to be flat. +// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27) +// However, our output is structured with css, js and media folders. +// To have this structure working with relative paths, we have to use custom options. +const extractTextPluginOptions = shouldUseRelativeAssetPaths + ? // Making sure that the publicPath goes back to to build folder. + { publicPath: Array(cssFilename.split('/').length).join('../') } + : {}; + +function generateIndexLocations() { + return paths.entries.map( + entry => + new HtmlWebpackPlugin({ + inject: true, + template: paths.templateIndexHtml, + chunks: [entry], + filename: `${entry}/index.html` + }) + ); +} + +// This is the production configuration. +// It compiles slowly and is focused on producing a fast and minimal bundle. +// The development configuration is different and lives in a separate file. +module.exports = { + // Don't attempt to continue if there are any errors. + bail: true, + // We generate sourcemaps in production. This is slow but gives good results. + // You can exclude the *.map files from the build during deployment. + devtool: shouldUseSourceMap ? 'source-map' : false, + // In production, we only want to load the polyfills and the app code. + entry: { + ...paths.webpackEntriesProdFn() + }, + output: { + // The build folder. + path: paths.appBuild, + // Generated JS file names (with nested folders). + // There will be one main bundle, and one file per asynchronous chunk. + // We don't currently advertise code splitting but Webpack supports it. + filename: '[name]/js/[name].js', + chunkFilename: '[name]/js/[name].chunk.js', + // We inferred the "public path" (such as / or /my-project) from homepage. + publicPath: publicPath, + // Point sourcemap entries to original disk location (format as URL on Windows) + devtoolModuleFilenameTemplate: info => + path.relative(paths.appSrc, info.absoluteResourcePath).replace(/\\/g, '/') + }, + resolve: { + // This allows you to set a fallback for where Webpack should look for modules. + // We placed these paths second because we want `node_modules` to "win" + // if there are any conflicts. This matches Node resolution mechanism. + // https://github.com/facebookincubator/create-react-app/issues/253 + modules: ['node_modules', paths.appNodeModules].concat( + // It is guaranteed to exist because we tweak it in `env.js` + process.env.NODE_PATH.split(path.delimiter).filter(Boolean) + ), + // These are the reasonable defaults supported by the Node ecosystem. + // We also include JSX as a common component filename extension to support + // some tools, although we do not recommend using it, see: + // https://github.com/facebookincubator/create-react-app/issues/290 + // `web` extension prefixes have been added for better support + // for React Native Web. + extensions: [ + '.mjs', + '.web.ts', + '.ts', + '.web.tsx', + '.tsx', + '.web.js', + '.js', + '.json', + '.web.jsx', + '.jsx' + ], + alias: { + // Support React Native Web + // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ + 'react-native': 'react-native-web' + }, + plugins: [ + // Prevents users from importing files from outside of src/ (or node_modules/). + // This often causes confusion because we only process files within src/ with babel. + // To fix this, we prevent you from importing files out of src/ -- if you'd like to, + // please link the files into your node_modules/ and let module-resolution kick in. + // Make sure your source files are compiled, as they will not be processed in any way. + new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), + new TsconfigPathsPlugin({ configFile: paths.appTsConfig }) + ] + }, + module: { + strictExportPresence: true, + rules: [ + // TODO: Disable require.ensure as it's not a standard language feature. + // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. + // { parser: { requireEnsure: false } }, + { + test: /\.(js|jsx|mjs)$/, + loader: require.resolve('source-map-loader'), + enforce: 'pre', + include: paths.appSrc + }, + { + // "oneOf" will traverse all following loaders until one will + // match the requirements. When no loader matches it will fall + // back to the "file" loader at the end of the loader list. + oneOf: [ + // "url" loader works just like "file" loader but it also embeds + // assets smaller than specified size as data URLs to avoid requests. + { + test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], + loader: require.resolve('url-loader'), + options: { + limit: 10000, + name: 'media/[name].[ext]' + } + }, + { + test: /\.(js|jsx|mjs)$/, + include: paths.appSrc, + loader: require.resolve('babel-loader'), + options: { + compact: true + } + }, + // Compile .tsx? + { + test: /\.(ts|tsx)$/, + include: paths.appSrc, + use: [ + { + loader: require.resolve('ts-loader'), + options: { + // disable type checker - we will use it in fork plugin + transpileOnly: true, + configFile: paths.appTsProdConfig + } + } + ] + }, + // The notation here is somewhat confusing. + // "postcss" loader applies autoprefixer to our CSS. + // "css" loader resolves paths in CSS and adds assets as dependencies. + // "style" loader normally turns CSS into JS modules injecting <style>, + // but unlike in development configuration, we do something different. + // `ExtractTextPlugin` first applies the "postcss" and "css" loaders + // (second argument), then grabs the result CSS and puts it into a + // separate file in our build process. This way we actually ship + // a single CSS file in production instead of JS code injecting <style> + // tags. If you use code splitting, however, any async bundles will still + // use the "style" loader inside the async code so CSS from them won't be + // in the main CSS file. + { + test: /\.css$/, + loader: ExtractTextPlugin.extract( + Object.assign( + { + fallback: { + loader: require.resolve('style-loader'), + options: { + hmr: false + } + }, + use: [ + { + loader: require.resolve('css-loader'), + options: { + importLoaders: 1, + minimize: true, + sourceMap: shouldUseSourceMap + } + }, + { + loader: require.resolve('postcss-loader'), + options: { + // Necessary for external CSS imports to work + // https://github.com/facebookincubator/create-react-app/issues/2677 + ident: 'postcss', + plugins: () => [ + require('postcss-flexbugs-fixes'), + autoprefixer({ + browsers: [ + '>1%', + 'last 4 versions', + 'Firefox ESR', + 'not ie < 9' // React doesn't support IE8 anyway + ], + flexbox: 'no-2009' + }) + ] + } + } + ] + }, + extractTextPluginOptions + ) + ) + // Note: this won't work without `new ExtractTextPlugin()` in `plugins`. + }, + // "file" loader makes sure assets end up in the `build` folder. + // When you `import` an asset, you get its filename. + // This loader doesn't use a "test" so it will catch all modules + // that fall through the other loaders. + { + loader: require.resolve('file-loader'), + // Exclude `js` files to keep "css" loader working as it injects + // it's runtime that would otherwise processed through "file" loader. + // Also exclude `html` and `json` extensions so they get processed + // by webpacks internal loaders. + exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/], + options: { + name: 'media/[name].[ext]' + } + } + // ** STOP ** Are you adding a new loader? + // Make sure to add the new loader(s) before the "file" loader. + ] + } + ] + }, + plugins: [ + // Makes some environment variables available in index.html. + // The public URL is available as %PUBLIC_URL% in index.html, e.g.: + // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> + // In production, it will be an empty string unless you specify "homepage" + // in `package.json`, in which case it will be the pathname of that URL. + new InterpolateHtmlPlugin(env.raw), + ...generateIndexLocations(), + // Makes some environment variables available to the JS code, for example: + // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`. + // It is absolutely essential that NODE_ENV was set to production here. + // Otherwise React will be compiled in the very slow development mode. + new webpack.DefinePlugin(env.stringified), + // Minify the code. + new UglifyJsPlugin({ + uglifyOptions: { + parse: { + // we want uglify-js to parse ecma 8 code. However we want it to output + // ecma 5 compliant code, to avoid issues with older browsers, this is + // whey we put `ecma: 5` to the compress and output section + // https://github.com/facebook/create-react-app/pull/4234 + ecma: 8 + }, + compress: { + ecma: 5, + warnings: false, + // Disabled because of an issue with Uglify breaking seemingly valid code: + // https://github.com/facebook/create-react-app/issues/2376 + // Pending further investigation: + // https://github.com/mishoo/UglifyJS2/issues/2011 + comparisons: false + }, + mangle: { + safari10: true + }, + output: { + ecma: 5, + comments: false, + // Turned on because emoji and regex is not minified properly using default + // https://github.com/facebook/create-react-app/issues/2488 + ascii_only: true + } + }, + // Use multi-process parallel running to improve the build speed + // Default number of concurrent runs: os.cpus().length - 1 + parallel: true, + // Enable file caching + cache: true, + sourceMap: shouldUseSourceMap + }), // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`. + new ExtractTextPlugin({ + filename: cssFilename + }), + // Moment.js is an extremely popular library that bundles large locale files + // by default due to how Webpack interprets its code. This is a practical + // solution that requires the user to opt into importing specific locales. + // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack + // You can remove this if you don't use Moment.js: + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + // Perform type checking and linting in a separate process to speed up compilation + new ForkTsCheckerWebpackPlugin({ + async: false, + tsconfig: paths.appTsProdConfig, + tslint: paths.appTsLint + }) + ], + // Some libraries import Node modules but don't use them in the browser. + // Tell Webpack to provide empty mocks for them so importing them works. + node: { + dgram: 'empty', + fs: 'empty', + net: 'empty', + tls: 'empty', + child_process: 'empty' + } +}; diff --git a/packages/salesforcedx-webview-ui/config/webpackDevServer.config.js b/packages/salesforcedx-webview-ui/config/webpackDevServer.config.js new file mode 100644 index 0000000000..7b1e5b28b5 --- /dev/null +++ b/packages/salesforcedx-webview-ui/config/webpackDevServer.config.js @@ -0,0 +1,95 @@ +'use strict'; + +const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); +const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); +const ignoredFiles = require('react-dev-utils/ignoredFiles'); +const config = require('./webpack.config.dev'); +const paths = require('./paths'); + +const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; +const host = process.env.HOST || '0.0.0.0'; + +module.exports = function(proxy, allowedHost) { + return { + // WebpackDevServer 2.4.3 introduced a security fix that prevents remote + // websites from potentially accessing local content through DNS rebinding: + // https://github.com/webpack/webpack-dev-server/issues/887 + // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a + // However, it made several existing use cases such as development in cloud + // environment or subdomains in development significantly more complicated: + // https://github.com/facebookincubator/create-react-app/issues/2271 + // https://github.com/facebookincubator/create-react-app/issues/2233 + // While we're investigating better solutions, for now we will take a + // compromise. Since our WDS configuration only serves files in the `public` + // folder we won't consider accessing them a vulnerability. However, if you + // use the `proxy` feature, it gets more dangerous because it can expose + // remote code execution vulnerabilities in backends like Django and Rails. + // So we will disable the host check normally, but enable it if you have + // specified the `proxy` setting. Finally, we let you override it if you + // really know what you're doing with a special environment variable. + disableHostCheck: !proxy || + process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true', + // Enable gzip compression of generated files. + compress: true, + // Silence WebpackDevServer's own logs since they're generally not useful. + // It will still show compile warnings and errors with this setting. + clientLogLevel: 'none', + // By default WebpackDevServer serves physical files from current directory + // in addition to all the virtual build products that it serves from memory. + // This is confusing because those files won’t automatically be available in + // production build folder unless we copy them. However, copying the whole + // project directory is dangerous because we may expose sensitive files. + // Instead, we establish a convention that only files in `public` directory + // get served. Our build script will copy `public` into the `build` folder. + // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: + // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> + // In JavaScript code, you can access it with `process.env.PUBLIC_URL`. + // Note that we only recommend to use `public` folder as an escape hatch + // for files like `favicon.ico`, `manifest.json`, and libraries that are + // for some reason broken when imported through Webpack. If you just want to + // use an image, put it in `src` and `import` it from JavaScript instead. + contentBase: paths.appTemplates, + // By default files from `contentBase` will not trigger a page reload. + watchContentBase: true, + // Enable hot reloading server. It will provide /sockjs-node/ endpoint + // for the WebpackDevServer client so it can learn when the files were + // updated. The WebpackDevServer client is included as an entry point + // in the Webpack development configuration. Note that only changes + // to CSS are currently hot reloaded. JS changes will refresh the browser. + hot: true, + // It is important to tell WebpackDevServer to use the same "root" path + // as we specified in the config. In development, we always serve from /. + publicPath: config.output.publicPath, + // WebpackDevServer is noisy by default so we emit custom message instead + // by listening to the compiler events with `compiler.plugin` calls above. + quiet: true, + // Reportedly, this avoids CPU overload on some systems. + // https://github.com/facebookincubator/create-react-app/issues/293 + // src/node_modules is not ignored to support absolute imports + // https://github.com/facebookincubator/create-react-app/issues/1065 + watchOptions: { + ignored: ignoredFiles(paths.appSrc), + }, + // Enable HTTPS if the HTTPS environment variable is set to 'true' + https: protocol === 'https', + host: host, + overlay: false, + historyApiFallback: { + // Paths with dots should still use the history fallback. + // See https://github.com/facebookincubator/create-react-app/issues/387. + disableDotRule: true, + }, + public: allowedHost, + proxy, + before(app) { + // This lets us open files from the runtime error overlay. + app.use(errorOverlayMiddleware()); + // This service worker file is effectively a 'no-op' that will reset any + // previous service worker registered for the same host:port combination. + // We do this in development to avoid hitting the production cache if + // it used the same host and port. + // https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432 + app.use(noopServiceWorkerMiddleware()); + }, + }; +}; diff --git a/packages/salesforcedx-webview-ui/images.d.ts b/packages/salesforcedx-webview-ui/images.d.ts new file mode 100644 index 0000000000..397cc9b35b --- /dev/null +++ b/packages/salesforcedx-webview-ui/images.d.ts @@ -0,0 +1,3 @@ +declare module '*.svg' +declare module '*.png' +declare module '*.jpg' diff --git a/packages/salesforcedx-webview-ui/package.json b/packages/salesforcedx-webview-ui/package.json new file mode 100644 index 0000000000..bce291dc86 --- /dev/null +++ b/packages/salesforcedx-webview-ui/package.json @@ -0,0 +1,111 @@ +{ + "name": "@salesforce/salesforcedx-webview-ui", + "version": "42.18.0", + "private": true, + "dependencies": { + "@blueprintjs/core": "2.3.1", + "autoprefixer": "7.1.6", + "babel-jest": "22.1.0", + "babel-loader": "7.1.2", + "babel-preset-react-app": "3.1.1", + "case-sensitive-paths-webpack-plugin": "2.1.1", + "chalk": "1.1.3", + "css-loader": "0.28.7", + "dotenv": "4.0.0", + "dotenv-expand": "4.2.0", + "extract-text-webpack-plugin": "3.0.2", + "file-loader": "0.11.2", + "fork-ts-checker-webpack-plugin": "0.2.8", + "fs-extra": "3.0.1", + "html-webpack-plugin": "2.29.0", + "jest": "22.4.2", + "object-assign": "4.1.1", + "postcss-flexbugs-fixes": "3.2.0", + "postcss-loader": "2.0.8", + "promise": "8.0.1", + "raf": "3.4.0", + "react": "16.3.2", + "react-dev-utils": "5.0.1", + "react-dom": "16.3.2", + "react-transition-group": "2.3.1", + "resolve": "1.6.0", + "source-map-loader": "0.2.1", + "style-loader": "0.19.0", + "ts-jest": "22.0.1", + "ts-loader": "2.3.7", + "tsconfig-paths-webpack-plugin": "2.0.0", + "tslint": "5.7.0", + "tslint-config-prettier": "1.10.0", + "tslint-react": "3.2.0", + "uglifyjs-webpack-plugin": "1.1.8", + "url-loader": "0.6.2", + "webpack": "3.8.1", + "webpack-dev-server": "2.9.4", + "whatwg-fetch": "2.0.3" + }, + "scripts": { + "compile": "node scripts/build.js", + "lint": "tslint --project .", + "start": "node scripts/start.js", + "clean": + "shx rm -rf node_modules && shx rm -rf out && shx rm -rf coverage && shx rm -rf .nyc_output", + "build": "node scripts/build.js", + "test": "node scripts/test.js --env=jsdom" + }, + "devDependencies": { + "@salesforce/dev-config": "1.1.0", + "@types/jest": "22.2.3", + "@types/node": "10.1.2", + "@types/react": "16.3.14", + "@types/react-dom": "16.0.5", + "cross-env": "5.0.4", + "typescript": "2.8.3" + }, + "jest": { + "collectCoverageFrom": ["src/**/*.{js,jsx,ts,tsx}"], + "setupFiles": ["<rootDir>/config/polyfills.js"], + "testMatch": [ + "<rootDir>/src/**/__tests__/**/*.(j|t)s?(x)", + "<rootDir>/src/**/?(*.)(spec|test).(j|t)s?(x)" + ], + "testEnvironment": "node", + "testURL": "http://localhost", + "transform": { + ".+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest", + ".+\\.tsx?$": "<rootDir>/config/jest/typescriptTransform.js", + ".+\\.css$": "<rootDir>/config/jest/cssTransform.js", + "(?!.*\\.(js|jsx|mjs|css|json)$)": + "<rootDir>/config/jest/fileTransform.js" + }, + "transformIgnorePatterns": [ + "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|ts|tsx)$" + ], + "moduleNameMapper": { + "react-native$": "react-native-web" + }, + "moduleFileExtensions": [ + "web.ts", + "ts", + "web.tsx", + "tsx", + "web.js", + "js", + "web.jsx", + "jsx", + "json", + "node", + "mjs" + ], + "globals": { + "ts-jest": { + "tsConfigFile": "tsconfig.test.json" + } + } + }, + "babel": { + "presets": ["react-app"] + }, + "eslintConfig": { + "extends": "react-app" + } +} diff --git a/packages/salesforcedx-webview-ui/scripts/build.js b/packages/salesforcedx-webview-ui/scripts/build.js new file mode 100644 index 0000000000..a5be48df88 --- /dev/null +++ b/packages/salesforcedx-webview-ui/scripts/build.js @@ -0,0 +1,144 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'production'; +process.env.NODE_ENV = 'production'; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + +const path = require('path'); +const chalk = require('chalk'); +const fs = require('fs-extra'); +const webpack = require('webpack'); +const config = require('../config/webpack.config.prod'); +const paths = require('../config/paths'); +const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); +const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); +const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); +const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); +const printBuildError = require('react-dev-utils/printBuildError'); + +const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild; +const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; +const useYarn = fs.existsSync(paths.yarnLockFile); + +// These sizes are pretty large. We'll warn for bundles exceeding them. +const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; +const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; + +// First, read the current file sizes in build directory. +// This lets us display how much they changed later. +measureFileSizesBeforeBuild(paths.appBuild) + .then(previousFileSizes => { + // Remove all content but keep the directory so that + // if you're in it, you don't end up in Trash + fs.emptyDirSync(paths.appBuild); + // Merge with the public folder + copyPublicFolder(); + // Start the webpack build + return build(previousFileSizes); + }) + .then( + ({ stats, previousFileSizes, warnings }) => { + if (warnings.length) { + console.log(chalk.yellow('Compiled with warnings.\n')); + console.log(warnings.join('\n\n')); + console.log( + '\nSearch for the ' + + chalk.underline(chalk.yellow('keywords')) + + ' to learn more about each warning.' + ); + console.log( + 'To ignore, add ' + + chalk.cyan('// eslint-disable-next-line') + + ' to the line before.\n' + ); + } else { + console.log(chalk.green('Compiled successfully.\n')); + } + + console.log('File sizes after gzip:\n'); + printFileSizesAfterBuild( + stats, + previousFileSizes, + paths.appBuild, + WARN_AFTER_BUNDLE_GZIP_SIZE, + WARN_AFTER_CHUNK_GZIP_SIZE + ); + console.log(); + + const appPackage = require(paths.appPackageJson); + const publicUrl = paths.publicUrl; + const publicPath = config.output.publicPath; + const buildFolder = path.relative(process.cwd(), paths.appBuild); + printHostingInstructions( + appPackage, + publicUrl, + publicPath, + buildFolder, + useYarn + ); + }, + err => { + console.log(chalk.red('Failed to compile.\n')); + printBuildError(err); + process.exit(1); + } + ); + +// Create the production build and print the deployment instructions. +function build(previousFileSizes) { + console.log('Creating an optimized production build...'); + + let compiler = webpack(config); + return new Promise((resolve, reject) => { + compiler.run((err, stats) => { + if (err) { + return reject(err); + } + const messages = formatWebpackMessages(stats.toJson({}, true)); + if (messages.errors.length) { + // Only keep the first error. Others are often indicative + // of the same problem, but confuse the reader with noise. + if (messages.errors.length > 1) { + messages.errors.length = 1; + } + return reject(new Error(messages.errors.join('\n\n'))); + } + if ( + process.env.CI && + (typeof process.env.CI !== 'string' || + process.env.CI.toLowerCase() !== 'false') && + messages.warnings.length + ) { + console.log( + chalk.yellow( + '\nTreating warnings as errors because process.env.CI = true.\n' + + 'Most CI servers set it automatically.\n' + ) + ); + return reject(new Error(messages.warnings.join('\n\n'))); + } + return resolve({ + stats, + previousFileSizes, + warnings: messages.warnings, + }); + }); + }); +} + +function copyPublicFolder() { + fs.copySync(paths.appTemplates, paths.appBuild, { + dereference: true, + filter: file => file !== paths.templateIndexHtml, + }); +} diff --git a/packages/salesforcedx-webview-ui/scripts/start.js b/packages/salesforcedx-webview-ui/scripts/start.js new file mode 100644 index 0000000000..885c2bfdfe --- /dev/null +++ b/packages/salesforcedx-webview-ui/scripts/start.js @@ -0,0 +1,102 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'development'; +process.env.NODE_ENV = 'development'; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + +const fs = require('fs'); +const chalk = require('chalk'); +const webpack = require('webpack'); +const WebpackDevServer = require('webpack-dev-server'); +const clearConsole = require('react-dev-utils/clearConsole'); +const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); +const { + choosePort, + createCompiler, + prepareProxy, + prepareUrls, +} = require('react-dev-utils/WebpackDevServerUtils'); +const openBrowser = require('react-dev-utils/openBrowser'); +const paths = require('../config/paths'); +const config = require('../config/webpack.config.dev'); +const createDevServerConfig = require('../config/webpackDevServer.config'); + +const useYarn = fs.existsSync(paths.yarnLockFile); +const isInteractive = process.stdout.isTTY; + +// Tools like Cloud9 rely on this. +const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; +const HOST = process.env.HOST || '0.0.0.0'; + +if (process.env.HOST) { + console.log( + chalk.cyan( + `Attempting to bind to HOST environment variable: ${chalk.yellow( + chalk.bold(process.env.HOST) + )}` + ) + ); + console.log( + `If this was unintentional, check that you haven't mistakenly set it in your shell.` + ); + console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`); + console.log(); +} + +// We attempt to use the default port but if it is busy, we offer the user to +// run on a different port. `choosePort()` Promise resolves to the next free port. +choosePort(HOST, DEFAULT_PORT) + .then(port => { + if (port == null) { + // We have not found a port. + return; + } + const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; + const appName = require(paths.appPackageJson).name; + const urls = prepareUrls(protocol, HOST, port); + // Create a webpack compiler that is configured with custom messages. + const compiler = createCompiler(webpack, config, appName, urls, useYarn); + // Load proxy config + const proxySetting = require(paths.appPackageJson).proxy; + const proxyConfig = prepareProxy(proxySetting, paths.appTemplates); + // Serve webpack assets generated by the compiler over a web sever. + const serverConfig = createDevServerConfig( + proxyConfig, + urls.lanUrlForConfig + ); + const devServer = new WebpackDevServer(compiler, serverConfig); + // Launch WebpackDevServer. + devServer.listen(port, HOST, err => { + if (err) { + return console.log(err); + } + if (isInteractive) { + clearConsole(); + } + console.log(chalk.cyan('Starting the development server...\n')); + openBrowser(urls.localUrlForBrowser); + }); + + ['SIGINT', 'SIGTERM'].forEach(function(sig) { + process.on(sig, function() { + devServer.close(); + process.exit(); + }); + }); + }) + .catch(err => { + if (err && err.message) { + console.log(err.message); + } + process.exit(1); + }); diff --git a/packages/salesforcedx-webview-ui/scripts/test.js b/packages/salesforcedx-webview-ui/scripts/test.js new file mode 100644 index 0000000000..841ab0f304 --- /dev/null +++ b/packages/salesforcedx-webview-ui/scripts/test.js @@ -0,0 +1,31 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'test'; +process.env.NODE_ENV = 'test'; +process.env.PUBLIC_URL = ''; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + +const jest = require('jest'); +let argv = process.argv.slice(2); + +// Watch unless on CI, in coverage mode, or explicitly running all tests +if ( + !process.env.CI && + argv.indexOf('--coverage') === -1 && + argv.indexOf('--watchAll') === -1 +) { + argv.push('--watch'); +} + + +jest.run(argv); diff --git a/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.test.tsx b/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.test.tsx new file mode 100644 index 0000000000..e0f09ab57a --- /dev/null +++ b/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.test.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(<App />, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.tsx b/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.tsx new file mode 100644 index 0000000000..cdc3c0f0d4 --- /dev/null +++ b/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.tsx @@ -0,0 +1,35 @@ +import { + Alignment, + Button, + Classes, + Navbar, + NavbarDivider, + NavbarGroup, + NavbarHeading +} from '@blueprintjs/core'; +import * as React from 'react'; +import '../../../node_modules/@blueprintjs/core/lib/css/blueprint.css'; +import '../../../node_modules/@blueprintjs/icons/lib/css/blueprint-icons.css'; +import '../../../node_modules/normalize.css/normalize.css'; + +class App extends React.Component { + public render() { + return ( + <div className="App"> + <header className="App-header"> + <h1>Manifest Editor</h1> + </header> + <Navbar> + <NavbarGroup align={Alignment.LEFT}> + <NavbarHeading>Blueprint</NavbarHeading> + <NavbarDivider /> + <Button className={Classes.MINIMAL} icon="home" text="Home" /> + <Button className={Classes.MINIMAL} icon="document" text="Files" /> + </NavbarGroup> + </Navbar> + </div> + ); + } +} + +export default App; diff --git a/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/index.css b/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/index.css new file mode 100644 index 0000000000..b4cc7250b9 --- /dev/null +++ b/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/index.css @@ -0,0 +1,5 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} diff --git a/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/index.tsx b/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/index.tsx new file mode 100644 index 0000000000..42532fd0a6 --- /dev/null +++ b/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/index.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import App from './App'; +import './index.css'; + +ReactDOM.render(<App />, document.getElementById('root') as HTMLElement); diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/App.css b/packages/salesforcedx-webview-ui/src/entries/Sample/App.css new file mode 100644 index 0000000000..c5c6e8a68a --- /dev/null +++ b/packages/salesforcedx-webview-ui/src/entries/Sample/App.css @@ -0,0 +1,28 @@ +.App { + text-align: center; +} + +.App-logo { + animation: App-logo-spin infinite 20s linear; + height: 80px; +} + +.App-header { + background-color: #222; + height: 150px; + padding: 20px; + color: white; +} + +.App-title { + font-size: 1.5em; +} + +.App-intro { + font-size: large; +} + +@keyframes App-logo-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/App.test.tsx b/packages/salesforcedx-webview-ui/src/entries/Sample/App.test.tsx new file mode 100644 index 0000000000..e0f09ab57a --- /dev/null +++ b/packages/salesforcedx-webview-ui/src/entries/Sample/App.test.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(<App />, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/App.tsx b/packages/salesforcedx-webview-ui/src/entries/Sample/App.tsx new file mode 100644 index 0000000000..452f589203 --- /dev/null +++ b/packages/salesforcedx-webview-ui/src/entries/Sample/App.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import './App.css'; + +import logo from './logo.svg'; + +class App extends React.Component { + public render() { + return ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <h1 className="App-title">Welcome to React</h1> + </header> + <p className="App-intro"> + To get started, edit <code>src/entries/dos/App.tsx</code> and save to + reload. + </p> + </div> + ); + } +} + +export default App; diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/index.css b/packages/salesforcedx-webview-ui/src/entries/Sample/index.css new file mode 100644 index 0000000000..b4cc7250b9 --- /dev/null +++ b/packages/salesforcedx-webview-ui/src/entries/Sample/index.css @@ -0,0 +1,5 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/index.tsx b/packages/salesforcedx-webview-ui/src/entries/Sample/index.tsx new file mode 100644 index 0000000000..42532fd0a6 --- /dev/null +++ b/packages/salesforcedx-webview-ui/src/entries/Sample/index.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import App from './App'; +import './index.css'; + +ReactDOM.render(<App />, document.getElementById('root') as HTMLElement); diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/logo.svg b/packages/salesforcedx-webview-ui/src/entries/Sample/logo.svg new file mode 100644 index 0000000000..6b60c1042f --- /dev/null +++ b/packages/salesforcedx-webview-ui/src/entries/Sample/logo.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"> + <g fill="#61DAFB"> + <path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/> + <circle cx="420.9" cy="296.5" r="45.7"/> + <path d="M520.5 78.1z"/> + </g> +</svg> diff --git a/packages/salesforcedx-webview-ui/templates/index.html b/packages/salesforcedx-webview-ui/templates/index.html new file mode 100644 index 0000000000..f829211270 --- /dev/null +++ b/packages/salesforcedx-webview-ui/templates/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <meta name="theme-color" content="#000000"> + </head> + <body> + <div id="root"></div> + </body> +</html> diff --git a/packages/salesforcedx-webview-ui/tsconfig.json b/packages/salesforcedx-webview-ui/tsconfig.json new file mode 100644 index 0000000000..bf312b6ef1 --- /dev/null +++ b/packages/salesforcedx-webview-ui/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "es6", + "lib": ["dom", "es2016"], + "baseUrl": ".", + "outDir": "out", + "sourceMap": true, + "jsx": "react", + "moduleResolution": "node", + "rootDir": "src", + "strict": true + }, + "exclude": [ + "node_modules", + "build", + "scripts", + "acceptance-tests", + "webpack", + "jest" + ] +} diff --git a/packages/salesforcedx-webview-ui/tsconfig.prod.json b/packages/salesforcedx-webview-ui/tsconfig.prod.json new file mode 100644 index 0000000000..4144216dd1 --- /dev/null +++ b/packages/salesforcedx-webview-ui/tsconfig.prod.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} \ No newline at end of file diff --git a/packages/salesforcedx-webview-ui/tsconfig.test.json b/packages/salesforcedx-webview-ui/tsconfig.test.json new file mode 100644 index 0000000000..2c7b284162 --- /dev/null +++ b/packages/salesforcedx-webview-ui/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs" + } +} diff --git a/packages/salesforcedx-webview-ui/tslint.json b/packages/salesforcedx-webview-ui/tslint.json new file mode 100644 index 0000000000..dd8bd166e0 --- /dev/null +++ b/packages/salesforcedx-webview-ui/tslint.json @@ -0,0 +1,10 @@ +{ + "extends": "@salesforce/dev-config/tslint", + "rules": { + "no-reserved-keywords": false, + "no-any": false, + "quotemark": [true, "single", "avoid-escape", "jsx-double"], + "member-ordering": false, + "no-string-based-set-timeout": false + } +} From 221e79d12bb9bf8eb4754ad5534fbff1e514137d Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Thu, 31 May 2018 16:51:31 -0700 Subject: [PATCH 02/17] Add commands to help build and copy things over --- .vscode/tasks.json | 7 +++++++ package.json | 8 ++++++-- packages/salesforcedx-vscode-core/.gitignore | 1 + packages/salesforcedx-webview-ui/package.json | 3 ++- 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 packages/salesforcedx-vscode-core/.gitignore diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c1657adce0..02f2903b97 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -73,6 +73,13 @@ "isShellCommand": true, "args": ["run", "start-webview"], "problemMatcher": "$tsc-watch" + }, + { + "taskName": "Bundle salesforcedx-webview-ui artifacts", + "command": "npm", + "isShellCommand": true, + "args": ["run", "bundle-webview"], + "problemMatcher": "$tsc-watch" } ] } diff --git a/package.json b/package.json index 5f10c6bff2..038a24da7b 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "bootstrap": "lerna bootstrap -- --no-package-lock && node scripts/reformat-with-prettier", "clean": "lerna run clean", - "compile": "lerna run --stream compile", + "compile": + "lerna run --stream compile --ignore salesforcedx-webview-ui && node run bundle-webview", "lint": "lerna run lint", "publish": "node scripts/publish.js", "prepush": "npm run lint", @@ -41,7 +42,10 @@ "eslint-check": "eslint --print-config .eslintrc.json | eslint-config-prettier-check", "reformat": "node scripts/reformat-with-prettier.js", - "start-webview": "npm start --prefix=packages/salesforcedx-webview-ui" + "start-webview": "npm run start --prefix=packages/salesforcedx-webview-ui", + "build-webview": "npm run build --prefix=packages/salesforcedx-webview-ui", + "bundle-webview": + "npm run compile --prefix=packages/salesforcedx-webview-ui && shx cp -R packages/salesforcedx-webview-ui/build packages/salesforcedx-vscode-core/webviews" }, "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-webview-ui/package.json b/packages/salesforcedx-webview-ui/package.json index bce291dc86..c4595f70a8 100644 --- a/packages/salesforcedx-webview-ui/package.json +++ b/packages/salesforcedx-webview-ui/package.json @@ -44,7 +44,8 @@ "whatwg-fetch": "2.0.3" }, "scripts": { - "compile": "node scripts/build.js", + "compile": + "./node_modules/.bin/cross-env PUBLIC_URL='__salesforcedx-vscode-core-prefix__' node scripts/build.js", "lint": "tslint --project .", "start": "node scripts/start.js", "clean": From 640eb2a4e2659619dff065e172667911a413ac0c Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Thu, 31 May 2018 17:18:54 -0700 Subject: [PATCH 03/17] Bump VS Core Requirement to 1.23 to support WebView --- packages/salesforcedx-vscode-lightning/package.json | 2 +- packages/salesforcedx-vscode-lwc-next/package.json | 2 +- packages/salesforcedx-vscode-lwc/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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", From d311f1b5dc03278449df34a652040aee4ca9ccd9 Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Fri, 1 Jun 2018 12:07:38 -0700 Subject: [PATCH 04/17] Clarify the new steps for developing --- docs/developing.md | 35 +++++++-- package.json | 8 +-- .../salesforcedx-vscode-core/package.json | 13 ++++ .../salesforcedx-vscode-core/package.nls.json | 7 +- .../src/messages/i18n.ts | 3 +- .../src/webviewPanels/manifestEditor.ts | 71 +++++++++++++++++++ packages/salesforcedx-webview-ui/package.json | 4 +- 7 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts 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 038a24da7b..0a21e2faf7 100644 --- a/package.json +++ b/package.json @@ -16,12 +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 --ignore salesforcedx-webview-ui && node run bundle-webview", + "lerna run --stream --parallel --ignore @salesforce/salesforcedx-webview-ui compile", "lint": "lerna run lint", "publish": "node scripts/publish.js", "prepush": "npm run lint", @@ -45,7 +45,7 @@ "start-webview": "npm run start --prefix=packages/salesforcedx-webview-ui", "build-webview": "npm run build --prefix=packages/salesforcedx-webview-ui", "bundle-webview": - "npm run compile --prefix=packages/salesforcedx-webview-ui && shx cp -R packages/salesforcedx-webview-ui/build packages/salesforcedx-vscode-core/webviews" + "lerna run --scope @salesforce/salesforcedx-webview-ui compile && shx cp -R packages/salesforcedx-webview-ui/build packages/salesforcedx-vscode-core/webviews" }, "repository": { "type": "git", diff --git a/packages/salesforcedx-vscode-core/package.json b/packages/salesforcedx-vscode-core/package.json index 55b1675c2d..39a04e695c 100644 --- a/packages/salesforcedx-vscode-core/package.json +++ b/packages/salesforcedx-vscode-core/package.json @@ -288,6 +288,10 @@ { "command": "sfdx.force.apex.log.get", "when": "sfdx:project_opened" + }, + { + "command": "sfdx.force.manifest.editor.show", + "when": "config.salesforcedx-vscode-core.show_experimental_webviews" } ] }, @@ -451,6 +455,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 +469,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/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..d77ae4ec3b --- /dev/null +++ b/packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts @@ -0,0 +1,71 @@ +/* + * 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 path from 'path'; +import * as vscode from 'vscode'; +import { nls } from '../../src/messages'; + +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 extensionPath: string; + private disposables: vscode.Disposable[] = []; + + public static createOrShow(extensionPath: string) { + const column = vscode.window.activeTextEditor + ? vscode.window.activeTextEditor.viewColumn + : undefined; + + if (ManifestEditor.currentPanel) { + ManifestEditor.currentPanel.panel.reveal(column); + } else { + ManifestEditor.currentPanel = new ManifestEditor( + extensionPath, + column || vscode.ViewColumn.One + ); + } + } + + private constructor(extensionPath: string, column: vscode.ViewColumn) { + this.extensionPath = extensionPath; + + this.panel = vscode.window.createWebviewPanel( + ManifestEditor.viewType, + ManifestEditor.viewTitle, + column, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [ + vscode.Uri.file(path.join(this.extensionPath, 'webviews')) + ] + } + ); + } + + public dispose() { + ManifestEditor.currentPanel = undefined; + + // Clean up our resources + this.panel.dispose(); + + while (this.disposables.length) { + const disposable = this.disposables.pop(); + if (disposable) { + disposable.dispose(); + } + } + } + + private show(): any { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/salesforcedx-webview-ui/package.json b/packages/salesforcedx-webview-ui/package.json index c4595f70a8..e4d7a797af 100644 --- a/packages/salesforcedx-webview-ui/package.json +++ b/packages/salesforcedx-webview-ui/package.json @@ -45,11 +45,11 @@ }, "scripts": { "compile": - "./node_modules/.bin/cross-env PUBLIC_URL='__salesforcedx-vscode-core-prefix__' node scripts/build.js", + "./node_modules/.bin/cross-env PUBLIC_URL='__salesforcedx-vscode-core-prefix__' node scripts/build.js &&shx cp -R build ../../packages/salesforcedx-vscode-core/webviews", "lint": "tslint --project .", "start": "node scripts/start.js", "clean": - "shx rm -rf node_modules && shx rm -rf out && shx rm -rf coverage && shx rm -rf .nyc_output", + "shx rm -rf node_modules && shx rm -rf out && shx rm -rf build && shx rm -rf coverage && shx rm -rf .nyc_output", "build": "node scripts/build.js", "test": "node scripts/test.js --env=jsdom" }, From 409d0dfdb90a1576e488c7d3926b520e0d7cf2ee Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Fri, 1 Jun 2018 13:02:29 -0700 Subject: [PATCH 05/17] Prevent double copying during build of salesforcedx-webviews-ui --- package.json | 2 +- packages/salesforcedx-webview-ui/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0a21e2faf7..2dd85876da 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "start-webview": "npm run start --prefix=packages/salesforcedx-webview-ui", "build-webview": "npm run build --prefix=packages/salesforcedx-webview-ui", "bundle-webview": - "lerna run --scope @salesforce/salesforcedx-webview-ui compile && shx cp -R packages/salesforcedx-webview-ui/build packages/salesforcedx-vscode-core/webviews" + "lerna run --scope @salesforce/salesforcedx-webview-ui compile" }, "repository": { "type": "git", diff --git a/packages/salesforcedx-webview-ui/package.json b/packages/salesforcedx-webview-ui/package.json index e4d7a797af..2d0b64ae1c 100644 --- a/packages/salesforcedx-webview-ui/package.json +++ b/packages/salesforcedx-webview-ui/package.json @@ -45,7 +45,7 @@ }, "scripts": { "compile": - "./node_modules/.bin/cross-env PUBLIC_URL='__salesforcedx-vscode-core-prefix__' node scripts/build.js &&shx cp -R build ../../packages/salesforcedx-vscode-core/webviews", + "./node_modules/.bin/cross-env PUBLIC_URL='__salesforcedx-vscode-core-prefix__' node scripts/build.js && shx rm -rf ../../salesforcedx-vscode-core/webview && shx cp -R build ../../packages/salesforcedx-vscode-core/webviews", "lint": "tslint --project .", "start": "node scripts/start.js", "clean": From c27e91fd4dc6838d07d5175882cb0fee7f24d5d0 Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Fri, 1 Jun 2018 13:15:41 -0700 Subject: [PATCH 06/17] Remove unnecessary sample from salesforcedx-webview-ui --- .../salesforcedx-webview-ui/config/paths.js | 2 +- packages/salesforcedx-webview-ui/package.json | 2 +- .../src/entries/Sample/App.css | 28 ------------------- .../src/entries/Sample/App.test.tsx | 9 ------ .../src/entries/Sample/App.tsx | 23 --------------- .../src/entries/Sample/index.css | 5 ---- .../src/entries/Sample/index.tsx | 6 ---- .../src/entries/Sample/logo.svg | 7 ----- 8 files changed, 2 insertions(+), 80 deletions(-) delete mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/App.css delete mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/App.test.tsx delete mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/App.tsx delete mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/index.css delete mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/index.tsx delete mode 100644 packages/salesforcedx-webview-ui/src/entries/Sample/logo.svg diff --git a/packages/salesforcedx-webview-ui/config/paths.js b/packages/salesforcedx-webview-ui/config/paths.js index f99d8a32a1..8f8b945bb2 100644 --- a/packages/salesforcedx-webview-ui/config/paths.js +++ b/packages/salesforcedx-webview-ui/config/paths.js @@ -41,7 +41,7 @@ function getServedPath(appPackageJson) { /////////////////////////////////////// // Add the different entry points here /////////////////////////////////////// -const entries = ['ManifestEditor', 'Sample']; +const entries = ['ManifestEditor']; // config after eject: we're in ./config/ module.exports = { diff --git a/packages/salesforcedx-webview-ui/package.json b/packages/salesforcedx-webview-ui/package.json index 2d0b64ae1c..77d78ecee0 100644 --- a/packages/salesforcedx-webview-ui/package.json +++ b/packages/salesforcedx-webview-ui/package.json @@ -45,7 +45,7 @@ }, "scripts": { "compile": - "./node_modules/.bin/cross-env PUBLIC_URL='__salesforcedx-vscode-core-prefix__' node scripts/build.js && shx rm -rf ../../salesforcedx-vscode-core/webview && shx cp -R build ../../packages/salesforcedx-vscode-core/webviews", + "./node_modules/.bin/cross-env PUBLIC_URL='__salesforcedx-vscode-core-prefix__' node scripts/build.js && shx rm -rf ../../packages/salesforcedx-vscode-core/webviews && shx cp -R build ../../packages/salesforcedx-vscode-core/webviews", "lint": "tslint --project .", "start": "node scripts/start.js", "clean": diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/App.css b/packages/salesforcedx-webview-ui/src/entries/Sample/App.css deleted file mode 100644 index c5c6e8a68a..0000000000 --- a/packages/salesforcedx-webview-ui/src/entries/Sample/App.css +++ /dev/null @@ -1,28 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - animation: App-logo-spin infinite 20s linear; - height: 80px; -} - -.App-header { - background-color: #222; - height: 150px; - padding: 20px; - color: white; -} - -.App-title { - font-size: 1.5em; -} - -.App-intro { - font-size: large; -} - -@keyframes App-logo-spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/App.test.tsx b/packages/salesforcedx-webview-ui/src/entries/Sample/App.test.tsx deleted file mode 100644 index e0f09ab57a..0000000000 --- a/packages/salesforcedx-webview-ui/src/entries/Sample/App.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import App from './App'; - -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(<App />, div); - ReactDOM.unmountComponentAtNode(div); -}); diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/App.tsx b/packages/salesforcedx-webview-ui/src/entries/Sample/App.tsx deleted file mode 100644 index 452f589203..0000000000 --- a/packages/salesforcedx-webview-ui/src/entries/Sample/App.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from 'react'; -import './App.css'; - -import logo from './logo.svg'; - -class App extends React.Component { - public render() { - return ( - <div className="App"> - <header className="App-header"> - <img src={logo} className="App-logo" alt="logo" /> - <h1 className="App-title">Welcome to React</h1> - </header> - <p className="App-intro"> - To get started, edit <code>src/entries/dos/App.tsx</code> and save to - reload. - </p> - </div> - ); - } -} - -export default App; diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/index.css b/packages/salesforcedx-webview-ui/src/entries/Sample/index.css deleted file mode 100644 index b4cc7250b9..0000000000 --- a/packages/salesforcedx-webview-ui/src/entries/Sample/index.css +++ /dev/null @@ -1,5 +0,0 @@ -body { - margin: 0; - padding: 0; - font-family: sans-serif; -} diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/index.tsx b/packages/salesforcedx-webview-ui/src/entries/Sample/index.tsx deleted file mode 100644 index 42532fd0a6..0000000000 --- a/packages/salesforcedx-webview-ui/src/entries/Sample/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import App from './App'; -import './index.css'; - -ReactDOM.render(<App />, document.getElementById('root') as HTMLElement); diff --git a/packages/salesforcedx-webview-ui/src/entries/Sample/logo.svg b/packages/salesforcedx-webview-ui/src/entries/Sample/logo.svg deleted file mode 100644 index 6b60c1042f..0000000000 --- a/packages/salesforcedx-webview-ui/src/entries/Sample/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"> - <g fill="#61DAFB"> - <path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/> - <circle cx="420.9" cy="296.5" r="45.7"/> - <path d="M520.5 78.1z"/> - </g> -</svg> From 06fc480b5bc32a98c9f2e97d3c59689cd85f2475 Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Fri, 1 Jun 2018 16:21:32 -0700 Subject: [PATCH 07/17] Initial working version Nothing fancy but it showcases the behavior and ability. --- .../salesforcedx-vscode-core/src/index.ts | 13 ++- .../src/webviewPanels/manifestEditor.ts | 79 ++++++++++++++++--- packages/salesforcedx-webview-ui/package.json | 5 +- .../src/entries/ManifestEditor/App.tsx | 14 ++-- 4 files changed, 91 insertions(+), 20 deletions(-) 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/webviewPanels/manifestEditor.ts b/packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts index d77ae4ec3b..8743442159 100644 --- a/packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts +++ b/packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts @@ -5,21 +5,24 @@ * 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'; -class ManifestEditor { +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 extensionPath: string; + private readonly extensionContext: vscode.ExtensionContext; private disposables: vscode.Disposable[] = []; - public static createOrShow(extensionPath: string) { + public static createOrShow(extensionContext: vscode.ExtensionContext) { const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; @@ -28,14 +31,17 @@ class ManifestEditor { ManifestEditor.currentPanel.panel.reveal(column); } else { ManifestEditor.currentPanel = new ManifestEditor( - extensionPath, + extensionContext, column || vscode.ViewColumn.One ); } } - private constructor(extensionPath: string, column: vscode.ViewColumn) { - this.extensionPath = extensionPath; + private constructor( + extensionContext: vscode.ExtensionContext, + column: vscode.ViewColumn + ) { + this.extensionContext = extensionContext; this.panel = vscode.window.createWebviewPanel( ManifestEditor.viewType, @@ -45,16 +51,31 @@ class ManifestEditor { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [ - vscode.Uri.file(path.join(this.extensionPath, 'webviews')) + 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) { + this.update(); + } + }, + null, + this.disposables + ); + + this.show(); } public dispose() { ManifestEditor.currentPanel = undefined; - // Clean up our resources this.panel.dispose(); while (this.disposables.length) { @@ -65,7 +86,45 @@ class ManifestEditor { } } - private show(): any { - throw new Error('Method not implemented.'); + private async update() { + this.show(); + } + + private async show() { + this.panel.webview.html = await this.getNormalizedResourceContents( + 'webviews/ManifestEditor/index.html' + ); + } + + private async getNormalizedResourceContents( + resourcePath: string + ): Promise<string> { + 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<string> { + return new Promise<string>((resolve, reject) => { + fs.readFile( + this.extensionContext.asAbsolutePath(resourcePath), + 'utf8', + (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + } + ); + }); } } diff --git a/packages/salesforcedx-webview-ui/package.json b/packages/salesforcedx-webview-ui/package.json index 77d78ecee0..b96881f7b4 100644 --- a/packages/salesforcedx-webview-ui/package.json +++ b/packages/salesforcedx-webview-ui/package.json @@ -1,6 +1,9 @@ { "name": "@salesforce/salesforcedx-webview-ui", + "description": "UI components for Salesforce Extensions for VS Code", "version": "42.18.0", + "publisher": "salesforce", + "license": "BSD-3-Clause", "private": true, "dependencies": { "@blueprintjs/core": "2.3.1", @@ -45,7 +48,7 @@ }, "scripts": { "compile": - "./node_modules/.bin/cross-env PUBLIC_URL='__salesforcedx-vscode-core-prefix__' node scripts/build.js && shx rm -rf ../../packages/salesforcedx-vscode-core/webviews && shx cp -R build ../../packages/salesforcedx-vscode-core/webviews", + "./node_modules/.bin/cross-env PUBLIC_URL='__salesforcedx-vscode-core-prefix__/webviews/' node scripts/build.js && shx rm -rf ../../packages/salesforcedx-vscode-core/webviews && shx cp -R build ../../packages/salesforcedx-vscode-core/webviews", "lint": "tslint --project .", "start": "node scripts/start.js", "clean": diff --git a/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.tsx b/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.tsx index cdc3c0f0d4..78ed2446e6 100644 --- a/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.tsx +++ b/packages/salesforcedx-webview-ui/src/entries/ManifestEditor/App.tsx @@ -3,9 +3,7 @@ import { Button, Classes, Navbar, - NavbarDivider, - NavbarGroup, - NavbarHeading + NavbarGroup } from '@blueprintjs/core'; import * as React from 'react'; import '../../../node_modules/@blueprintjs/core/lib/css/blueprint.css'; @@ -21,10 +19,12 @@ class App extends React.Component { </header> <Navbar> <NavbarGroup align={Alignment.LEFT}> - <NavbarHeading>Blueprint</NavbarHeading> - <NavbarDivider /> - <Button className={Classes.MINIMAL} icon="home" text="Home" /> - <Button className={Classes.MINIMAL} icon="document" text="Files" /> + <Button className={Classes.MINIMAL} icon="list" text="Basic" /> + <Button + className={Classes.MINIMAL} + icon="list-detail-view" + text="Advanced" + /> </NavbarGroup> </Navbar> </div> From 8e6e5b6787f2bd726b4ebefe87257dc234f524c1 Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Fri, 1 Jun 2018 16:36:51 -0700 Subject: [PATCH 08/17] Fix tslint errors --- .../src/webviewPanels/manifestEditor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts b/packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts index 8743442159..ad0a0631e2 100644 --- a/packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts +++ b/packages/salesforcedx-vscode-core/src/webviewPanels/manifestEditor.ts @@ -63,14 +63,14 @@ export class ManifestEditor { this.panel.onDidChangeViewState( event => { if (this.panel.visible) { - this.update(); + void this.update(); } }, null, this.disposables ); - this.show(); + void this.show(); } public dispose() { @@ -87,7 +87,7 @@ export class ManifestEditor { } private async update() { - this.show(); + void this.show(); } private async show() { From 96db4edbaeba2012a0e13cd4ffa431f62e1e0de5 Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Fri, 1 Jun 2018 16:37:01 -0700 Subject: [PATCH 09/17] Update to Task 2.0 format --- .vscode/tasks.json | 79 ++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 02f2903b97..c02e0289fb 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,29 +1,38 @@ { - "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": { @@ -41,43 +50,53 @@ } }, { - "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 - }, - { - "taskName": "Watch", - "command": "npm", - "isShellCommand": true, - "showOutput": "silent", + "type": "shell", + "presentation": { + "reveal": "silent", + "focus": false, + "panel": "dedicated" + }, "args": ["run", "watch"], "isBackground": true, "problemMatcher": "$tsc-watch" }, { - "taskName": "Start salesforcedx-webview-ui server", + "label": "Start salesforcedx-webview-ui server", "command": "npm", + "type": "shell", "isBackground": true, - "isShellCommand": true, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "dedicated" + }, "args": ["run", "start-webview"], "problemMatcher": "$tsc-watch" }, { - "taskName": "Bundle salesforcedx-webview-ui artifacts", + "label": "Bundle salesforcedx-webview-ui artifacts", "command": "npm", - "isShellCommand": true, + "type": "shell", + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "dedicated" + }, "args": ["run", "bundle-webview"], "problemMatcher": "$tsc-watch" } From dadf1b31bf4c27e15177d7a7d2c8513e9d3dbd53 Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Fri, 1 Jun 2018 16:43:44 -0700 Subject: [PATCH 10/17] Compile needs to be serial due to dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2dd85876da..ea6ccfe2aa 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "lerna bootstrap -- --no-package-lock && npm run bundle-webview && node scripts/reformat-with-prettier", "clean": "lerna run clean", "compile": - "lerna run --stream --parallel --ignore @salesforce/salesforcedx-webview-ui compile", + "lerna run --stream --ignore @salesforce/salesforcedx-webview-ui compile", "lint": "lerna run lint", "publish": "node scripts/publish.js", "prepush": "npm run lint", From 3222faa2c0973b94a34995489169c8da4867ee7e Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Fri, 1 Jun 2018 17:59:32 -0700 Subject: [PATCH 11/17] Add README --- .vscode/tasks.json | 14 ++ package.json | 3 +- packages/salesforcedx-webview-ui/README.md | 122 ++++++++++++++++++ packages/salesforcedx-webview-ui/package.json | 3 + 4 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 packages/salesforcedx-webview-ui/README.md diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c02e0289fb..8550dfcd76 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -73,6 +73,20 @@ "isBackground": true, "problemMatcher": "$tsc-watch" }, + { + "label": "Test salesforcedx-webview-ui", + "command": "npm", + "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", diff --git a/package.json b/package.json index ea6ccfe2aa..40e5e5f985 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,9 @@ "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": - "lerna run --scope @salesforce/salesforcedx-webview-ui compile" + "npm run compile --prefix=packages/salesforcedx-webview-ui" }, "repository": { "type": "git", diff --git a/packages/salesforcedx-webview-ui/README.md b/packages/salesforcedx-webview-ui/README.md new file mode 100644 index 0000000000..8790af7378 --- /dev/null +++ b/packages/salesforcedx-webview-ui/README.md @@ -0,0 +1,122 @@ +# 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 their [Extensibility Principles and +Patterns](https://code.visualstudio.com/docs/extensionAPI/patterns-and-principles#_extensibility-api). + +However, there are times when we do need to provide our own custom UI within VS +Code. Fortunately, VS Code 1.23 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 + +``` ++--------------------------------+ +-------------------------------+ +| | | | +| +-----------+ +-----------+ | | | +| | | | | | postMessage | | +| | Cmp1 | | Cmp2 | +-------------------> | +| | | | | | | | +| +-----------+ +-----------+ | | | +| +--------------------------+ | | | +| | | | | | +| | | | | | +| | Cmp3 | | | | +| | | | | | +| | | | | | +| | | | postMessage | | +| +--------------------------+ <-------------------+ | +| | | | ++--------------------------------+ +-------------------------------+ + + WebviewPanel VS Code Extension +``` + +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. + +# 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 + +# 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. + +### Debugging + +You can use the Chrome Developer with React Developer Tools 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/package.json b/packages/salesforcedx-webview-ui/package.json index b96881f7b4..2e6d3f5d7d 100644 --- a/packages/salesforcedx-webview-ui/package.json +++ b/packages/salesforcedx-webview-ui/package.json @@ -5,6 +5,9 @@ "publisher": "salesforce", "license": "BSD-3-Clause", "private": true, + "engines": { + "vscode": "^1.17.0" + }, "dependencies": { "@blueprintjs/core": "2.3.1", "autoprefixer": "7.1.6", From b5af5f11811763474e77cb8254a61bc2f32cdd3c Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Fri, 1 Jun 2018 18:10:53 -0700 Subject: [PATCH 12/17] Tie command to sfdx:project_opened Since this is tied to the salesforcedx-vscode-core extension that only loads when a sfdx-project.json is around. --- packages/salesforcedx-vscode-core/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/salesforcedx-vscode-core/package.json b/packages/salesforcedx-vscode-core/package.json index 39a04e695c..29aa413b9b 100644 --- a/packages/salesforcedx-vscode-core/package.json +++ b/packages/salesforcedx-vscode-core/package.json @@ -291,7 +291,8 @@ }, { "command": "sfdx.force.manifest.editor.show", - "when": "config.salesforcedx-vscode-core.show_experimental_webviews" + "when": + "sfdx:project_opened && config.salesforcedx-vscode-core.show_experimental_webviews" } ] }, From 72a85f3635e8b2d2bf3b3405fbf219a9079a325f Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Fri, 1 Jun 2018 20:31:38 -0700 Subject: [PATCH 13/17] Clarify README --- packages/salesforcedx-webview-ui/README.md | 117 ++++++++++++++++----- 1 file changed, 91 insertions(+), 26 deletions(-) diff --git a/packages/salesforcedx-webview-ui/README.md b/packages/salesforcedx-webview-ui/README.md index 8790af7378..21e1de1ce0 100644 --- a/packages/salesforcedx-webview-ui/README.md +++ b/packages/salesforcedx-webview-ui/README.md @@ -4,12 +4,13 @@ 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 their [Extensibility Principles and -Patterns](https://code.visualstudio.com/docs/extensionAPI/patterns-and-principles#_extensibility-api). +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 provide for this via the [VS Code WebView -API](https://code.visualstudio.com/docs/extensions/webview) +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. @@ -17,25 +18,77 @@ components, and we expose them as panels inside VS Code. # Architecture ``` -+--------------------------------+ +-------------------------------+ -| | | | -| +-----------+ +-----------+ | | | -| | | | | | postMessage | | -| | Cmp1 | | Cmp2 | +-------------------> | -| | | | | | | | -| +-----------+ +-----------+ | | | -| +--------------------------+ | | | -| | | | | | -| | | | | | -| | Cmp3 | | | | -| | | | | | -| | | | | | -| | | | postMessage | | -| +--------------------------+ <-------------------+ | -| | | | -+--------------------------------+ +-------------------------------+ - - WebviewPanel VS Code Extension +┌────────────────────────────────────────────────────┐ +│ │ +│ ┌───────────────────────────────────────────┐ │ +│ │ │ │ +│ │ ┌──────────────────┐ ┌──────────────────┐ │ │ +│ │ │ │ │ │ │ │ +│ │ │ 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. @@ -43,6 +96,10 @@ 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 @@ -50,6 +107,12 @@ message passing. The communication protocol is up to the implementor. * [Blueprint](http://blueprintjs.com/docs/v2/) - For UI components * [Jest](https://facebook.github.io/jest/) - For testing +# Useful Reading + +* [`<webview>` 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 @@ -64,12 +127,14 @@ accomplish this via an in-memory web server that watches for changes to files 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. +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 to debug. -The in-memory web server automatically includes the necessary source maps. +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 From 07024ccc7762ab1af2d1272ad581d002215987f4 Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Fri, 1 Jun 2018 20:37:40 -0700 Subject: [PATCH 14/17] Straighten diagram --- packages/salesforcedx-webview-ui/README.md | 136 ++++++++++----------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/packages/salesforcedx-webview-ui/README.md b/packages/salesforcedx-webview-ui/README.md index 21e1de1ce0..32fc449a79 100644 --- a/packages/salesforcedx-webview-ui/README.md +++ b/packages/salesforcedx-webview-ui/README.md @@ -18,74 +18,74 @@ 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 ++----------------------------------------------------+ +| | +| +-------------------------------------------+ | +| | | | +| | +------------------+ +------------------+ | | +| | | | | | | | +| | | 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 From 659224eeb12aa673ceb1bf4a67f12b258171f552 Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Tue, 5 Jun 2018 13:02:21 -0700 Subject: [PATCH 15/17] Restore react and react-dom to devDependencies --- packages/salesforcedx-webview-ui/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/salesforcedx-webview-ui/package.json b/packages/salesforcedx-webview-ui/package.json index 2e6d3f5d7d..897bcefe39 100644 --- a/packages/salesforcedx-webview-ui/package.json +++ b/packages/salesforcedx-webview-ui/package.json @@ -30,9 +30,7 @@ "postcss-loader": "2.0.8", "promise": "8.0.1", "raf": "3.4.0", - "react": "16.3.2", "react-dev-utils": "5.0.1", - "react-dom": "16.3.2", "react-transition-group": "2.3.1", "resolve": "1.6.0", "source-map-loader": "0.2.1", @@ -66,6 +64,8 @@ "@types/react": "16.3.14", "@types/react-dom": "16.0.5", "cross-env": "5.0.4", + "react": "16.3.2", + "react-dom": "16.3.2", "typescript": "2.8.3" }, "jest": { From 863ad30153042c4a0565a55805b8518ff66f2f2f Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Tue, 5 Jun 2018 13:09:35 -0700 Subject: [PATCH 16/17] Output and compress to es6 to enable tree-shaking --- .../salesforcedx-webview-ui/config/webpack.config.prod.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/salesforcedx-webview-ui/config/webpack.config.prod.js b/packages/salesforcedx-webview-ui/config/webpack.config.prod.js index c5685768cf..bd1972d046 100644 --- a/packages/salesforcedx-webview-ui/config/webpack.config.prod.js +++ b/packages/salesforcedx-webview-ui/config/webpack.config.prod.js @@ -274,14 +274,10 @@ module.exports = { new UglifyJsPlugin({ uglifyOptions: { parse: { - // we want uglify-js to parse ecma 8 code. However we want it to output - // ecma 5 compliant code, to avoid issues with older browsers, this is - // whey we put `ecma: 5` to the compress and output section - // https://github.com/facebook/create-react-app/pull/4234 ecma: 8 }, compress: { - ecma: 5, + ecma: 6, warnings: false, // Disabled because of an issue with Uglify breaking seemingly valid code: // https://github.com/facebook/create-react-app/issues/2376 @@ -293,7 +289,7 @@ module.exports = { safari10: true }, output: { - ecma: 5, + ecma: 6, comments: false, // Turned on because emoji and regex is not minified properly using default // https://github.com/facebook/create-react-app/issues/2488 From 23bf1c20c52988294971f59789447b3746f863dd Mon Sep 17 00:00:00 2001 From: Nick Chen <nchen@salesforce.com> Date: Tue, 5 Jun 2018 13:24:05 -0700 Subject: [PATCH 17/17] Add fonts (eot, ttf, woff) as list of items that can embedded --- .../config/webpack.config.dev.js | 14 +++++++++++--- .../config/webpack.config.prod.js | 12 ++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/salesforcedx-webview-ui/config/webpack.config.dev.js b/packages/salesforcedx-webview-ui/config/webpack.config.dev.js index 7a8d42a3f1..ba7e17621b 100644 --- a/packages/salesforcedx-webview-ui/config/webpack.config.dev.js +++ b/packages/salesforcedx-webview-ui/config/webpack.config.dev.js @@ -42,7 +42,7 @@ module.exports = { // You may want 'eval' instead if you prefer to see the compiled output in DevTools. // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343. devtool: 'cheap-module-source-map', - entry: paths.webpackEntriesDevFn , + entry: paths.webpackEntriesDevFn, output: { // Add /* filename */ comments to generated require()s in the output. pathinfo: true, @@ -122,10 +122,18 @@ module.exports = { // smaller than specified limit in bytes as data URLs to avoid requests. // A missing `test` is equivalent to a match. { - test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], + test: [ + /\.bmp$/, + /\.gif$/, + /\.jpe?g$/, + /\.png$/, + /\.eot$/, + /\.ttf$/, + /\.woff$/ + ], loader: require.resolve('url-loader'), options: { - limit: 10000, + limit: 100000, name: 'media/[name].[ext]' } }, diff --git a/packages/salesforcedx-webview-ui/config/webpack.config.prod.js b/packages/salesforcedx-webview-ui/config/webpack.config.prod.js index bd1972d046..a73a7b27ce 100644 --- a/packages/salesforcedx-webview-ui/config/webpack.config.prod.js +++ b/packages/salesforcedx-webview-ui/config/webpack.config.prod.js @@ -147,10 +147,18 @@ module.exports = { // "url" loader works just like "file" loader but it also embeds // assets smaller than specified size as data URLs to avoid requests. { - test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], + test: [ + /\.bmp$/, + /\.gif$/, + /\.jpe?g$/, + /\.png$/, + /\.eot$/, + /\.ttf$/, + /\.woff$/ + ], loader: require.resolve('url-loader'), options: { - limit: 10000, + limit: 100000, name: 'media/[name].[ext]' } },