├── .babelrc ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CONTRIBUTING.md ├── ISSUES.md ├── LICENSE.md ├── README.md ├── _config.yml ├── logo.png ├── package-lock.json ├── package.json ├── snappy-0.5.0.vsix ├── src ├── __test__ │ └── enzyme.test.tsx ├── extension.ts ├── functions │ ├── loadLiquidGauge.ts │ ├── optimizeFunctions.ts │ ├── traverseParseFunctions.ts │ └── webpackFunctions.ts ├── style │ └── styles.css └── views │ ├── App.tsx │ ├── components │ ├── Assets.tsx │ ├── Form.tsx │ ├── Visualizations.tsx │ └── visuals │ │ └── LiquidGauges.tsx │ ├── index.tsx │ └── tsconfig.json ├── tsconfig.json ├── tslint.json ├── vsc-extension-quickstart.md ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-typescript", 4 | "@babel/preset-env", 5 | "@babel/preset-react" 6 | ], 7 | "plugins": [ 8 | "@babel/plugin-transform-modules-commonjs", 9 | "@babel/plugin-transform-runtime", 10 | "@babel/proposal-class-properties", 11 | "@babel/proposal-object-rest-spread" 12 | ] 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | .DS_Store 4 | yarn.lock -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.vscode-typescript-tslint-plugin" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | 9 | { 10 | "name": "Run Extension", 11 | "type": "extensionHost", 12 | "request": "launch", 13 | "runtimeExecutable": "${execPath}", 14 | "args": [ 15 | "--extensionDevelopmentPath=${workspaceFolder}" 16 | ], 17 | "outFiles": [ 18 | "${workspaceFolder}/out/**/*.js" 19 | ], 20 | "preLaunchTask": "npm: watch" 21 | }, 22 | { 23 | "name": "Extension Tests", 24 | "type": "extensionHost", 25 | "request": "launch", 26 | "runtimeExecutable": "${execPath}", 27 | "args": [ 28 | "--extensionDevelopmentPath=${workspaceFolder}", 29 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 30 | ], 31 | "outFiles": [ 32 | "${workspaceFolder}/out/test/**/*.js" 33 | ], 34 | "preLaunchTask": "npm: watch" 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tslint.json 9 | **/*.map 10 | **/*.ts -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to snAppy 2 | We love your input! We want to make contributing to snAppy as easy and transparent as possible, whether it's: 3 | 4 | ### Reporting a bug 5 | Discussing the current state of the code 6 | Submitting a fix 7 | Proposing new features 8 | We Develop with Github 9 | We use Github to host code, to track issues and feature requests, as well as accept pull requests. 10 | 11 | All Code Changes Happen Through Pull Requests 12 | Pull requests are the best way to propose changes to snAppy. We actively welcome your pull requests: 13 | 14 | Fork the repo and create your branch from dev. 15 | If you've added code that should be tested, add tests. 16 | If you've changed APIs, update the documentation. 17 | Ensure the test suite passes. 18 | Make sure your code lints. 19 | Issue that pull request! 20 | Any contributions you make will be under the MIT Software License 21 | In short, when you submit code changes, your submissions are understood to be under the same that covers the project. Feel free to contact the maintainers if that's a concern. 22 | 23 | ### Report bugs using Github's issues 24 | We use GitHub issues to track public bugs. Report a bug by opening a new issue; it's that easy! 25 | 26 | Write bug reports with detail, background, and sample code 27 | Great Bug Reports tend to have: 28 | 29 | ### A quick summary and/or background 30 | Steps to reproduce 31 | Be specific! 32 | Give sample code if you can. Include sample code that anyone can run to reproduce what you are experiencing 33 | What you expected would happen 34 | What actually happens 35 | Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 36 | People love thorough bug reports. I'm not even kidding. 37 | 38 | ### Use a Consistent Coding Style 39 | 2 spaces for indentation rather than tabs 40 | You can try running npm run lint for style unification 41 | 42 | #### License 43 | By contributing, you agree that your contributions will be licensed under its MIT License. 44 | 45 | #### References 46 | This document was adapted from the open-source contribution guidelines for Facebook's Draft 47 | -------------------------------------------------------------------------------- /ISSUES.md: -------------------------------------------------------------------------------- 1 | ## Issue Template 2 | 3 | ### Prerequisites 4 | 5 | Please answer the following questions for yourself before submitting an issue. 6 | 7 | I am running the latest version 8 | I checked the documentation and found no answer 9 | I checked to make sure that this issue has not already been filed 10 | I'm reporting the issue to the correct repository (for multi-repository projects) 11 | 12 | ### Expected Behavior 13 | Please describe the behavior you are expecting 14 | 15 | ### Current Behavior 16 | What is the current behavior? 17 | 18 | ### Failure Information (for bugs) 19 | Please help provide information about the failure if this is a bug. If it is not a bug, please remove the rest of this template. 20 | 21 | ### Steps to Reproduce 22 | Please provide detailed steps for reproducing the issue. 23 | 24 | ### Failure Logs 25 | Please include any relevant log snippets or files here. 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 snAppy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # snAppy 2 | 3 | snAppy is a VS Code extension coupled with an interactive view to support your React front-end delivery. It automates dynamic and vendor code-splitting all within your workspace. 4 | 5 | ### Features 6 | 7 | snAppy includes Webpack configuration and bundling, automated dynamic/vendor code-splitting, and exporting of webpack.config.js and bundle report files. snAppy works on top of your code base, never deleting your code and you may choose to keep/delete any changes made. 8 | 9 | (gif/demo coming soon) 10 | 11 | ### Getting Started 12 | 13 | #### Extension Settings 14 | 15 | Pull down the Command Palette in your workspace/project and search: snAppy: Start on Current Workspace. Turn on auto-save to allow for rebundle of your application after optimizations have been made. You may still undo any changes afterwards. 16 | 17 | #### How to Use 18 | 19 | As recommended by the Webpack documentation, please name your output folder as "dist" and include your index.html inside. The script source should be "bundle.js". For the entry path within the Webview, please supply the relative path from the workspace's root folder to index.js. Please see below: 20 | 21 | 22 | 23 | ### Contributing and Issues 24 | We are always looking to improve. If there are any contributions or issues you have, please check out our documentation to submit. 25 | 26 | #### Release Notes 27 | Created by: Courtney Kwong, Jackie Lin, Olga Naumova, Rachel Park 28 |
0.5.0 | Initial release of snAppy. More to come! 29 | 30 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/snAppy/a9b853934bf932ec1f25c970fe13b0fbf3b8496d/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snappy", 3 | "displayName": "snAppy", 4 | "description": "Front-end Optimization Tool", 5 | "version": "0.5.0", 6 | "publisher": "snAppy", 7 | "engines": { 8 | "vscode": "^1.38.0" 9 | }, 10 | "icon": "logo.png", 11 | "repository": { 12 | "type": "git", 13 | "directory": "https://github.com/oslabs-beta/snAppy.git" 14 | }, 15 | "categories": [ 16 | "Other" 17 | ], 18 | "keywords": [ 19 | "front-end", 20 | "react", 21 | "optimization", 22 | "optimization tool", 23 | "front-end optimization tool", 24 | "load time optimization", 25 | "dynamic", 26 | "import" 27 | ], 28 | "activationEvents": [ 29 | "onCommand:extension.startSnappy" 30 | ], 31 | "main": "./out/extension.js", 32 | "contributes": { 33 | "commands": [ 34 | { 35 | "command": "extension.startSnappy", 36 | "title": "Start on Current Workspace", 37 | "category": "snAppy" 38 | } 39 | ] 40 | }, 41 | "scripts": { 42 | "vscode:prepublish": "npm run compile", 43 | "compile": "npm-run-all compile:*", 44 | "compile:extension": "tsc", 45 | "compile:views": "webpack --mode development", 46 | "watch": "tsc -watch -p ./", 47 | "pretest": "npm run compile", 48 | "test": "jest --verbose", 49 | "build": "webpack", 50 | "stats": "webpack --profile --json > compilation-stats.json" 51 | }, 52 | "jest": { 53 | "verbose": true 54 | }, 55 | "devDependencies": { 56 | "@babel/core": "^7.6.0", 57 | "@babel/preset-env": "^7.6.0", 58 | "@babel/preset-react": "^7.6.3", 59 | "@types/d3": "^5.7.2", 60 | "@types/glob": "^7.1.1", 61 | "@types/node": "^10.12.21", 62 | "@types/react": "^16.9.2", 63 | "@types/react-dom": "^16.9.0", 64 | "@types/vscode": "^1.38.0", 65 | "babel-loader": "^8.0.6", 66 | "css-loader": "^3.2.0", 67 | "enzyme": "^3.10.0", 68 | "enzyme-adapter-react-16": "^1.15.1", 69 | "glob": "^7.1.4", 70 | "style-loader": "^1.0.0", 71 | "ts-loader": "^6.1.0", 72 | "tslint": "^5.12.1", 73 | "typescript": "^3.3.1", 74 | "vscode-test": "^1.2.0" 75 | }, 76 | "dependencies": { 77 | "@babel/plugin-proposal-class-properties": "^7.5.5", 78 | "@babel/plugin-transform-runtime": "^7.6.2", 79 | "@babel/preset-typescript": "^7.6.0", 80 | "@babel/runtime": "^7.6.3", 81 | "@types/enzyme": "^3.10.3", 82 | "@types/enzyme-adapter-react-16": "^1.0.5", 83 | "@types/jest": "^24.0.19", 84 | "@types/webpack": "^4.39.5", 85 | "babel-plugin-transform-class-properties": "^6.24.1", 86 | "d3": "^5.12.0", 87 | "enzyme-to-json": "^3.4.3", 88 | "esprima": "^4.0.1", 89 | "jest": "^24.9.0", 90 | "npm-run-all": "^4.1.5", 91 | "react": "^16.9.0", 92 | "react-dom": "^16.9.0", 93 | "ts-import-plugin": "^1.6.1", 94 | "utf8": "^3.0.0", 95 | "vscode-uri": "^2.0.3", 96 | "webpack": "^4.40.2", 97 | "webpack-cli": "^3.3.8" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /snappy-0.5.0.vsix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/snAppy/a9b853934bf932ec1f25c970fe13b0fbf3b8496d/snappy-0.5.0.vsix -------------------------------------------------------------------------------- /src/__test__/enzyme.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {configure, shallow, ShallowWrapper} from 'enzyme'; 3 | import * as Adapter from 'enzyme-adapter-react-16'; 4 | import * as toJson from 'enzyme-to-json'; 5 | import Assets from '../views/components/Assets'; 6 | import Form from '../views/components/Form'; 7 | import Visualization from '../views/components/Visualizations'; 8 | import LiquidGauge from '../views/components/visuals/LiquidGauges'; 9 | 10 | import App from '../views/App'; 11 | 12 | configure({ adapter: new Adapter() }); 13 | 14 | describe('', () => { 15 | let wrapper: ShallowWrapper; 16 | beforeAll(()=>{ 17 | wrapper = shallow(); 18 | }) 19 | it('renders one components', () => { 20 | expect(wrapper.length).toEqual(1); 21 | }); 22 | it('renders one h1', () => { 23 | expect(wrapper.find('h1').length).toEqual(1); 24 | }); 25 | it('h1 renders logo in text', () => { 26 | expect(wrapper.find('h1').text()).toEqual('snAppy'); 27 | }); 28 | }); 29 | 30 | const clickForm = jest.fn(); 31 | 32 | describe('
', () => { 33 | interface Props { 34 | runFunc: Function; 35 | entryFunc: Function; 36 | entry: string; 37 | } 38 | let wrapper: ShallowWrapper; 39 | const runWebpackGetStats = jest.fn(); 40 | const entryFunc = jest.fn(); 41 | const entry = 'mock string'; 42 | 43 | const props = { 44 | runFunc: runWebpackGetStats, 45 | entryFunc: entryFunc, 46 | entry: entry 47 | }; 48 | beforeAll(()=> { 49 | wrapper=shallow(); 50 | }); 51 | it('is a div with a form', () => { 52 | expect(wrapper.find('form').length).toEqual(1); 53 | }); 54 | it('has an onClick function', () => { 55 | const fakeEvent = { preventDefault: () => console.log('preventDefault') }; 56 | wrapper 57 | .find('form') 58 | .simulate('submit', fakeEvent); 59 | expect(runWebpackGetStats).toHaveBeenCalled(); 60 | }); 61 | it('has an onChange function', () => { 62 | const fakeEvent = { 63 | target: { value: 'the-value' } 64 | }; 65 | wrapper 66 | .find('form') 67 | .find('input#entryInput') 68 | .simulate('change', fakeEvent); 69 | expect(entryFunc).toBeCalledWith(fakeEvent); 70 | }); 71 | }); 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, commands, window, ViewColumn, Uri, workspace } from 'vscode'; 2 | import { URI } from 'vscode-uri'; 3 | const { exec } = require('child_process'); 4 | import * as configs from "./functions/webpackFunctions"; 5 | import traverseAndDynamicallyImport from "./functions/traverseParseFunctions"; 6 | const path = require('path'); 7 | 8 | 9 | function loadScript(context: ExtensionContext, path: string) { 10 | return ``; 11 | } 12 | 13 | export function activate(context: ExtensionContext) { 14 | const startCommand = commands.registerCommand('extension.startSnappy', () => { 15 | const panel = window.createWebviewPanel('snAppy', 'snAppy!', ViewColumn.Beside, { enableScripts: true , retainContextWhenHidden: true}); 16 | //panel's html is created in function below (getWebviewContent) 17 | panel.webview.html = getWebviewContent(context); 18 | //ip to send messages/data from react frontend to node backend 19 | panel.webview.onDidReceiveMessage((message: any) => { 20 | interface Module { 21 | entry: string; 22 | css: boolean; 23 | jsx: boolean; 24 | less: boolean; 25 | sass: boolean; 26 | tsx: boolean; 27 | } 28 | switch (message.command) { 29 | //onClick(Bundle! button): build and get stats of application: 30 | case 'config': 31 | let moduleState: Module = { 32 | entry: message.entry, 33 | ...message.module, 34 | }; 35 | configs.runWriteWebpackBundle(moduleState, panel); 36 | break; 37 | //onClick(Optimize button): parses file using AST to locate static imports and replacing it with dynamic imports 38 | case 'optimize': 39 | let resolvedEntry = path.resolve(`${(workspace.workspaceFolders? workspace.workspaceFolders[0].uri.path : '/') + message.entry}`); 40 | traverseAndDynamicallyImport(resolvedEntry, resolvedEntry); 41 | return exec('npx webpack --profile --json > compilation-stats.json', {cwd: __dirname}, (err : Error, stdout: string)=>{ 42 | workspace.fs.readFile(URI.file(path.join(__dirname, 'compilation-stats.json'))) 43 | .then(res => { 44 | return panel.webview.postMessage({command: 'post', field: res.toString()}); 45 | }); 46 | }); 47 | break; 48 | case 'export': 49 | console.log('exporting files'); 50 | workspace.fs.createDirectory((URI.file(workspace.workspaceFolders? workspace.workspaceFolders[0].uri.path + '/snappy': '/'))); 51 | workspace.fs.readFile(URI.file(path.join(__dirname, 'webpack.config.js'))) 52 | .then( res => { 53 | workspace.fs.writeFile(URI.file(workspace.workspaceFolders? workspace.workspaceFolders[0].uri.path + '/snappy/webpack.config.js': '/'), res); 54 | }); 55 | workspace.fs.readFile(URI.file(path.join(__dirname, 'compilation-stats.json'))) 56 | .then( res => { 57 | workspace.fs.writeFile(URI.file(workspace.workspaceFolders? workspace.workspaceFolders[0].uri.path + '/snappy/compilation-stats.json': '/'), res); 58 | }); 59 | } 60 | }); 61 | }); 62 | context.subscriptions.push(startCommand); 63 | } 64 | 65 | function getWebviewContent(context: ExtensionContext) { 66 | return ` 67 | 68 | 69 | 70 | 71 | 72 | Snappy 73 | 74 | 75 | 76 |
77 | 80 | ${loadScript(context, 'out/snappy.js')} 81 | 82 | `; 83 | } 84 | 85 | 86 | export function deactivate() {} 87 | -------------------------------------------------------------------------------- /src/functions/loadLiquidGauge.ts: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | // import { Area } from 'd3'; 3 | 4 | interface ConfigI { 5 | minValue: number; 6 | maxValue: number; 7 | circleThickness: number; 8 | circleFillGap: number; 9 | circleColor: string; 10 | waveHeight: number; 11 | waveCount: number; 12 | waveRiseTime: number; 13 | waveAnimateTime: number; 14 | waveRise : boolean; 15 | waveHeightScaling: boolean; 16 | waveAnimate: boolean; 17 | waveColor: string; 18 | waveOffset: number; 19 | textVertPosition: number; 20 | textSize: number; 21 | valueCountUp: boolean; 22 | displayPercent: boolean; 23 | textColor: string; 24 | waveTextColor: string; 25 | } 26 | 27 | export function liquidFillGaugeDefaultSettings() : ConfigI { 28 | return { 29 | minValue: 0, // The gauge minimum value. 30 | maxValue: 100, // The gauge maximum value. 31 | 32 | circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius. 33 | // The size of the gap between the outer circle and wave circle as a percentage of 34 | // the outer circles radius. 35 | circleFillGap: 0.05, 36 | circleColor: "#178BCA", // The color of the outer circle. 37 | 38 | waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle. 39 | waveCount: 1, // The number of full waves per width of the wave circle. 40 | waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height. 41 | waveAnimateTime: 18000, // The amount of time in milliseconds for a full wave to enter the wave circle. 42 | 43 | // Control if the wave should rise from 0 to it's full height, or start at it's full height. 44 | waveRise: true, 45 | 46 | // Controls wave size scaling at low and high fill percentages. 47 | // When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill. 48 | // This helps to prevent the wave from making the wave circle from appear totally full or empty 49 | // when near it's minimum or maximum fill. 50 | waveHeightScaling: true, 51 | waveAnimate: true, // Controls if the wave scrolls or is static. 52 | waveColor: "#178BCA", // The color of the fill wave. 53 | waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave. 54 | 55 | // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top. 56 | textVertPosition: .5, 57 | textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50% 58 | 59 | // If true, the displayed value counts up from 0 to it's final value upon loading. 60 | // If false, the final value is displayed. 61 | valueCountUp: true, 62 | displayPercent: true, // If true, a % symbol is displayed after the value. 63 | textColor: "#045681", // The color of the value text when the wave does not overlap it. 64 | waveTextColor: "#A4DBf8" // The color of the value text when the wave overlaps it. 65 | }; 66 | } 67 | 68 | export function loadLiquidFillGauge(elementId: string, value:any, config:ConfigI, post:number) { 69 | if (config === undefined) { config = liquidFillGaugeDefaultSettings(); } 70 | 71 | const gauge = d3.select("#" + elementId); 72 | const radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height"))) / 2; 73 | 74 | const locationX = parseInt(gauge.style("width")) / 2 - radius; 75 | const locationY = parseInt(gauge.style("height")) / 2 - radius; 76 | const fillPercent = value / config.maxValue; 77 | 78 | let waveHeightScale : d3.ScaleLinear; 79 | if (config.waveHeightScaling) { 80 | waveHeightScale = d3.scaleLinear() 81 | .range([0, config.waveHeight, 0]) 82 | .domain([0, 50, 100]); 83 | } else { 84 | waveHeightScale = d3.scaleLinear() 85 | .range([config.waveHeight, config.waveHeight]) 86 | .domain([0, 100]); 87 | } 88 | 89 | const textPixels = (config.textSize * radius / 2); 90 | const textFinalValue = parseFloat(value).toFixed(2); 91 | const textStartValue = config.valueCountUp ? config.minValue : textFinalValue; 92 | const percentText = config.displayPercent ? "" : ""; 93 | const circleThickness = config.circleThickness * radius; 94 | const circleFillGap = config.circleFillGap * radius; 95 | const fillCircleMargin = circleThickness + circleFillGap; 96 | const fillCircleRadius = radius - fillCircleMargin; 97 | const waveHeight = fillCircleRadius * waveHeightScale(fillPercent * 100); 98 | 99 | const waveLength = fillCircleRadius * 2 / config.waveCount; 100 | const waveClipCount = 1 + config.waveCount; 101 | const waveClipWidth = waveLength * waveClipCount; 102 | 103 | // Rounding functions so that the correct number of decimal places is always displayed 104 | // as the value counts up. 105 | const format = d3.format(".0f"); 106 | 107 | // Data for building the clip wave area. 108 | const data = []; 109 | for (let i = 0; i <= 40 * waveClipCount; i++) { 110 | data.push({x: i / (40 * waveClipCount), y: (i / (40))}); 111 | } 112 | 113 | // Scales for drawing the outer circle. 114 | const gaugeCircleX = d3.scaleLinear().range([0, 2 * Math.PI]).domain([0, 1]); 115 | const gaugeCircleY = d3.scaleLinear().range([0, radius]).domain([0, radius]); 116 | 117 | // Scales for controlling the size of the clipping path. 118 | const waveScaleX = d3.scaleLinear().range([0, waveClipWidth]).domain([0, 1]); 119 | const waveScaleY = d3.scaleLinear().range([0, waveHeight]).domain([0, 1]); 120 | 121 | // Scales for controlling the position of the clipping path. 122 | const waveRiseScale = d3.scaleLinear() 123 | // The clipping area size is the height of the fill circle + the wave height, 124 | // so we position the clip wave such that the it will overlap the fill circle 125 | // at all when at 0%, and will totally cover the fill circle at 100%. 126 | .range([(fillCircleMargin + fillCircleRadius * 2 + waveHeight), (fillCircleMargin - waveHeight)]) 127 | .domain([0, 1]); 128 | 129 | const waveAnimateScale = d3.scaleLinear() 130 | .range([0, waveClipWidth - fillCircleRadius * 2]) // Push the clip area one full wave then snap back. 131 | .domain([0, 1]); 132 | 133 | // Scale for controlling the position of the text within the gauge. 134 | const textRiseScaleY = d3.scaleLinear() 135 | .range([fillCircleMargin + fillCircleRadius * 2,(fillCircleMargin + textPixels * 0.7)]) 136 | .domain([0, 1]); 137 | 138 | // Center the gauge within the parent SVG. 139 | const gaugeGroup = gauge.append("g") 140 | .attr('transform','translate(' + locationX + ',' + locationY + ')'); 141 | // console.log('GAUAGE GROUP: ', locationX); 142 | 143 | // Draw the outer circle. 144 | const gaugeCircleArc = d3.arc() 145 | .startAngle(gaugeCircleX(0)) 146 | .endAngle(gaugeCircleX(1)) 147 | .outerRadius(gaugeCircleY(radius)) 148 | .innerRadius(gaugeCircleY(radius - circleThickness)); 149 | 150 | gaugeGroup.append("path") 151 | .attr("d", gaugeCircleArc) 152 | .style("fill", config.circleColor) 153 | .attr('transform','translate(' + radius + ',' + radius + ')'); 154 | // console.log('gaugeGroup append PATH: ', radius); 155 | // Text where the wave does not overlap. 156 | gaugeGroup.append("text") 157 | .text(format(textStartValue) + percentText) 158 | .attr("class", "liquidFillGaugeText") 159 | .attr("text-anchor", "middle") 160 | .attr("font-size", textPixels + "px") 161 | .style("fill", config.textColor) 162 | .attr('transform','translate(' + radius + ',' + textRiseScaleY(config.textVertPosition) + ')'); 163 | 164 | // The clipping wave area. 165 | const clipArea = d3.area() 166 | .x(function(d:any) { return waveScaleX(d.x); }) 167 | .y0(function(d:any) { return waveScaleY(Math.sin(Math.PI * 2 * config.waveOffset * -1 + Math.PI * 2 * (1 - config.waveCount) + d.y * 2 * Math.PI));}) 168 | .y1(function(d) { return (fillCircleRadius *2 + waveHeight); }); 169 | const waveGroup = gaugeGroup.append("defs") 170 | .append("clipPath") 171 | .attr("id", "clipWave" + elementId); 172 | const wave = waveGroup.append("path") 173 | .datum(data) 174 | .attr("d", clipArea) 175 | .attr("T", 0); 176 | 177 | // The inner circle with the clipping wave attached. 178 | const fillCircleGroup = gaugeGroup.append("g") 179 | .attr("clip-path", "url(" + location.href + "#clipWave" + elementId + ")"); 180 | 181 | fillCircleGroup.append("circle") 182 | .attr("cx", radius) 183 | .attr("cy", radius) 184 | .attr("r", fillCircleRadius) 185 | .style("fill", config.waveColor); 186 | 187 | // Text where the wave does overlap. 188 | fillCircleGroup.append("text") 189 | .text(format(textStartValue)) 190 | .attr("class", "liquidFillGaugeText") 191 | .attr("text-anchor", "middle") 192 | .attr("font-size", textPixels + "px") 193 | .style("fill", config.waveTextColor) 194 | .attr('transform','translate(' + radius + ',' + textRiseScaleY(config.textVertPosition) + ')'); 195 | 196 | // Make the value count up. 197 | if (config.valueCountUp) { 198 | gaugeGroup.selectAll("text.liquidFillGaugeText").transition() 199 | .duration(config.waveRiseTime) 200 | .tween("text", function(d) { 201 | var that = d3.select(this); 202 | var i = d3.interpolateNumber(that.text().replace("", ""), textFinalValue); 203 | return function(t) { that.text(format(i(t)) + percentText); }; 204 | }); 205 | } 206 | 207 | // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement 208 | // can be controlled independently. 209 | const waveGroupXPosition = fillCircleMargin + fillCircleRadius * 2 - waveClipWidth; 210 | 211 | if (config.waveRise) { 212 | waveGroup.attr('transform','translate(' + waveGroupXPosition + ',' + waveRiseScale(0) + ')') 213 | .transition() 214 | .duration(config.waveRiseTime) 215 | .attr('transform','translate(' + waveGroupXPosition + ',' + waveRiseScale(fillPercent) + ')') 216 | .on("start", function() { wave.attr('transform','translate(1,0)'); }); 217 | // This transform is necessary to get the clip wave positioned correctly when 218 | // waveRise=true and waveAnimate=false. The wave will not position correctly without 219 | // this, but it's not clear why this is actually necessary. 220 | } else { 221 | waveGroup.attr('transform','translate(' + waveGroupXPosition + ',' + waveRiseScale(fillPercent) + ')'); 222 | } 223 | 224 | if(config.waveAnimate) { animateWave(); } 225 | 226 | function animateWave() { 227 | wave.attr('transform','translate(' + waveAnimateScale(wave.attr('T')) + ',0)'); 228 | wave.transition() 229 | .duration(config.waveAnimateTime * (1 - wave.attr('T'))) 230 | .ease(d3.easeLinear) 231 | .attr('transform','translate(' + waveAnimateScale(1) + ',0)') 232 | .attr('T', 1) 233 | .on('end', function() { 234 | wave.attr('T', 0); 235 | if (config.waveAnimate) { animateWave(); } 236 | }); 237 | } 238 | 239 | class GaugeUpdater { 240 | constructor(){ 241 | 242 | } 243 | setWaveAnimate(value:boolean) { 244 | // Note: must call update after setting value 245 | config.waveAnimate = value; 246 | } 247 | 248 | update (value:number) { 249 | gaugeGroup.selectAll("text.liquidFillGaugeText").transition() 250 | .duration(config.waveRiseTime) 251 | .tween("text", function(d) { 252 | var that = d3.select(this); 253 | var i = d3.interpolateNumber(that.text().replace("%", ""), value); 254 | return function(t) { that.text(format(i(t)) + percentText); }; 255 | }); 256 | 257 | var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value)) / config.maxValue; 258 | var waveHeight = fillCircleRadius * waveHeightScale(fillPercent * 100); 259 | var waveRiseScale = d3.scaleLinear() 260 | // The clipping area size is the height of the fill circle + the wave height, so we position 261 | // the clip wave such that the it will overlap the fill circle at all when at 0%, and will 262 | // totally cover the fill circle at 100%. 263 | .range([(fillCircleMargin + fillCircleRadius * 2 + waveHeight), (fillCircleMargin - waveHeight)]) 264 | .domain([0,1]); 265 | var newHeight = waveRiseScale(fillPercent); 266 | var waveScaleX = d3.scaleLinear().range([0, waveClipWidth]).domain([0, 1]); 267 | var waveScaleY = d3.scaleLinear().range([0, waveHeight]).domain([0, 1]); 268 | var newClipArea; 269 | 270 | if (config.waveHeightScaling) { 271 | newClipArea = d3.area() 272 | .x(function(d:any) { return waveScaleX(d.x); } ) 273 | .y0(function(d:any) { 274 | return waveScaleY(Math.sin( 275 | Math.PI * 2 * config.waveOffset * -1 + Math.PI * 2 * (1 - config.waveCount) + d.y * 2 * Math.PI)); 276 | }) 277 | .y1(function(d) { return (fillCircleRadius * 2 + waveHeight); }); 278 | } else { 279 | newClipArea = clipArea; 280 | } 281 | 282 | var newWavePosition = config.waveAnimate ? waveAnimateScale(1) : 0; 283 | wave.transition() 284 | .duration(0) 285 | .transition() 286 | .duration(config.waveAnimate ? (config.waveAnimateTime * (1 - wave.attr('T'))) : config.waveRiseTime) 287 | .ease(d3.easeLinear) 288 | .attr('d', newClipArea) 289 | .attr('transform','translate(' + newWavePosition + ',0)') 290 | .attr('T','1') 291 | .on("end", function() { 292 | if (config.waveAnimate) { 293 | wave.attr('transform','translate(' + waveAnimateScale(0) + ',0)'); 294 | animateWave(); 295 | } 296 | }); 297 | 298 | waveGroup.transition() 299 | .duration(config.waveRiseTime) 300 | .attr('transform','translate(' + waveGroupXPosition + ',' + newHeight + ')'); 301 | } 302 | } 303 | return new GaugeUpdater(); 304 | } -------------------------------------------------------------------------------- /src/functions/optimizeFunctions.ts: -------------------------------------------------------------------------------- 1 | import { WorkspaceEdit, workspace, Position, Uri } from "vscode"; 2 | 3 | 4 | export default function dynamicImportFunc(uri: Uri, uncommentLines: number[],exportLine: number, components: ComponentObject) { 5 | //will use that and the starting position to comment out static imports by using workspaceEdit.insert(URI, position, string) 6 | let edit = new WorkspaceEdit(); 7 | for (let line of uncommentLines) { 8 | edit.insert(uri, new Position(line - 1, 0), "//"); 9 | } 10 | workspace.applyEdit(edit).then(res => { 11 | let dynamicInjection = createDynamicInjection(components); 12 | insertFunc(uri, exportLine, dynamicInjection); 13 | }); 14 | } 15 | interface EachComponent { 16 | name: string; 17 | source: string; 18 | path: string 19 | } 20 | interface ComponentObject { 21 | [propName: string]: EachComponent 22 | } 23 | 24 | const createDynamicInjection = (componentObject: ComponentObject) => { 25 | //an outside function that loops through the object and for each key will execute the function below to create a new instance of class 26 | //will have a varibale "injection" with the class declaration 27 | let injection = `class DynamicImport extends Component { 28 | constructor(props) { 29 | super(props); 30 | this.state = { 31 | component: null 32 | } 33 | } 34 | componentDidMount() { 35 | this.props.load() 36 | .then((component) => { 37 | this.setState(()=> ({ 38 | component : component.default ? component.default : component 39 | })) 40 | }) 41 | } 42 | render () { 43 | return this.props.children(this.state.component) 44 | } 45 | } 46 | 47 | `; 48 | for (let val in componentObject) { 49 | injection += newInstance( 50 | componentObject[val].name, 51 | componentObject[val].source 52 | ); 53 | } 54 | //concatenate every new instance of class invoked with each key/values to injection string 55 | //return the resulting string with injection+ each instance of new class 56 | return injection; 57 | }; 58 | //inside of the outside func there will be a function that injects a string literal with values from the object into string declaration of new instance of DynamicImports class 59 | function newInstance(name: string, path: string) { 60 | return `const ${name} = (props) => ( 61 | import(/*webpackChunkName: "${name}-chunk"*/ '${path}')}>{ 62 | (Component) => Component === null 63 | ?

Loading..

64 | : 65 | }
66 | ) 67 | `; 68 | } 69 | 70 | const insertFunc = (uri: Uri, line: number, injection: string) => { 71 | let edit = new WorkspaceEdit(); 72 | edit.insert(uri, new Position(line - 1, 0), injection); 73 | workspace.applyEdit(edit).then(res => console.log("applyedit", res)); 74 | }; 75 | -------------------------------------------------------------------------------- /src/functions/traverseParseFunctions.ts: -------------------------------------------------------------------------------- 1 | import { workspace, Uri } from 'vscode'; 2 | import { URI } from 'vscode-uri'; 3 | import dynamicImportFunc from "./optimizeFunctions"; 4 | const esprima = require('esprima'); 5 | const path = require('path'); 6 | 7 | /* 8 | //traverse through the user's workspace and convert static import statements into dynamic imports 9 | originalEntry = path + /src/client/index.js 10 | entryPath = path, but mutates 11 | */ 12 | interface EachComponent { 13 | name: string; 14 | source: string; 15 | path: string 16 | } 17 | interface ComponentObject { 18 | [propName: string]: EachComponent 19 | } 20 | export default function traverseAndDynamicallyImport (originalEntry: string, entryPath: string) { 21 | let readURI: Uri = URI.file(entryPath); 22 | workspace.fs.readFile(readURI) 23 | .then((res: any) => { 24 | 25 | let holdingRes = res.toString(); 26 | let result = parseAST(esprima.parseModule(res.toString(), { tolerant: true, range: true, loc: true, jsx: true })); 27 | 28 | interface NewResults { 29 | components: ComponentObject; 30 | paths: string[]; 31 | importLineNumbers: number[] 32 | } 33 | let newResults: NewResults = findComponentsInFile(result.components, holdingRes, result.paths, result.importLineNumbers); 34 | if (entryPath !== originalEntry && newResults.importLineNumbers.length) { 35 | dynamicImportFunc(readURI,newResults.importLineNumbers, result.exportLineNumber, newResults.components); 36 | } 37 | 38 | if (newResults.paths.length > 0) { 39 | for (let i=0; i; 74 | components: ComponentObject; 75 | exportLineNumber: number; 76 | importLineNumbers: number[]; 77 | otherImports: ComponentObject ; 78 | } 79 | 80 | let resultObj: ResultObj = { 81 | paths:[], 82 | components:{}, 83 | exportLineNumber:0, 84 | importLineNumbers:[], 85 | otherImports:{} 86 | }; 87 | 88 | for (let i=0; i, importLineNumbers: Array) { 121 | let newResultObj: any = {}; 122 | let componentNames = Object.keys(componentsObj); 123 | for (let i=0; i { 40 | //moduleObj is the rules obj returned from reateModule and used in createWebpackConfig 41 | const moduleObj = createModule(moduleStateObj); 42 | const webpackConfigObject: WebpackConfig = createWebpackConfig(`${(workspace.workspaceFolders? workspace.workspaceFolders[0].uri.path : '/') + moduleStateObj.entry}`, moduleObj); 43 | //writing a new file called 'webpack.config.js': 44 | const writeUri = path.join(__dirname, '..', 'webpack.config.js'); 45 | //writing inside the file: 46 | workspace.fs.writeFile(URI.file(writeUri), new Uint8Array(Buffer.from( 47 | `const path = require('path'); 48 | module.exports =${util.inspect(webpackConfigObject, { depth: null })}`, 'utf-8', 49 | ))) 50 | .then(res => { 51 | window.showInformationMessage('Bundling...'); 52 | //run child process to execute script: 53 | return exec('npx webpack --profile --json > compilation-stats.json', {cwd: path.join(__dirname, '..')}, (err : Error, stdout: string)=>{ 54 | //read the file and when complete, send message to frontend 55 | workspace.fs.readFile(URI.file(path.join(__dirname, '..', 'compilation-stats.json'))) 56 | .then(res => { 57 | return panel.webview.postMessage({command: 'initial', field: res.toString()}); 58 | }); 59 | }); 60 | }); 61 | }; 62 | 63 | const createWebpackConfig = (entry: string, mod: Rules) => { 64 | const moduleExports: WebpackConfig = {}; 65 | moduleExports.entry = { 66 | main: entry, 67 | }; 68 | moduleExports.mode = 'development'; 69 | moduleExports.output = { 70 | filename: 'bundle.js', 71 | path: `${(workspace.workspaceFolders? workspace.workspaceFolders[0].uri.path : '/') + '/dist'}`, 72 | }; 73 | 74 | moduleExports.resolve = { 75 | extensions: ['.jsx', '.js', '.ts', '.tsx', '.json'], 76 | }; 77 | moduleExports.module = mod; 78 | return moduleExports; 79 | }; 80 | 81 | const createModule = (modules: ModuleState) => { 82 | const module: any = {}; 83 | module.rules = []; 84 | if (modules.css) { 85 | module.rules.push({ 86 | // keeping regex in string form so that we can pass it to another file 87 | // we are thinking to convert the string back to a regexpression right before injecting this code into another file 88 | test: /\.css$/i, 89 | use: ['style-loader', 'css-loader'], 90 | }); 91 | } 92 | if (modules.jsx) { 93 | module.rules.push({ 94 | test: /\.(js|jsx)$/, 95 | use: [{ 96 | loader: 'babel-loader', 97 | options: { 98 | presets: ['@babel/preset-env', '@babel/preset-react'], 99 | plugins: ['@babel/plugin-proposal-class-properties'] 100 | }, 101 | }], 102 | exclude: '/node_modules/', 103 | }); 104 | } 105 | 106 | if (modules.tsx) { 107 | module.rules.push({ 108 | test: /\.tsx?$/, 109 | use: ['ts-loader'], 110 | exclude: '/node_modules/', 111 | }); 112 | } 113 | if (modules.less) { 114 | module.rules.push({ 115 | test: /\.less$/, 116 | loader: 'less-loader', // compiles Less to CSS 117 | }); 118 | } 119 | if (modules.sass) { 120 | module.rules.push({ 121 | test: /\.s[ac]ss$/i, 122 | use: ['style-loader', 'css-loader', 'sass-loader'], 123 | }); 124 | } 125 | return module; 126 | }; -------------------------------------------------------------------------------- /src/style/styles.css: -------------------------------------------------------------------------------- 1 | #firstFormLabel{ 2 | font-size: 16px; 3 | color: white; 4 | font-weight: 600; 5 | } 6 | 7 | /* #mainApp{ 8 | width: 100%; 9 | height: 100%; 10 | display:flex; 11 | flex-direction: column; 12 | justify-content: center; 13 | align-items: flex-start; 14 | padding: 50px; 15 | } */ 16 | 17 | #formDiv { 18 | padding: 15px 15px; 19 | box-shadow: 0em; 20 | border: 2px solid white; 21 | border-radius: 10px; 22 | height: 150px; 23 | width: 400px; 24 | } 25 | 26 | #initialBundleResults { 27 | padding: 15px 15px; 28 | box-shadow: 0em; 29 | border: 2px solid white; 30 | border-radius: 10px; 31 | height: auto; 32 | width: 300px; 33 | 34 | } 35 | 36 | #finalBundleResults { 37 | padding: 15px 15px; 38 | box-shadow: 0em; 39 | border: 2px solid white; 40 | border-radius: 10px; 41 | height: auto; 42 | width: 300px; 43 | } 44 | 45 | #WPoptionLabel{ 46 | color: white; 47 | font-weight: 400; 48 | font-size: 14px; 49 | margin-right: 10px; 50 | } 51 | 52 | .submitButton{ 53 | font-size: 10px; 54 | outline: none; 55 | color: black; 56 | border-radius: 4px; 57 | } 58 | 59 | .submitButton :hover { 60 | background-color: #66b3ff; 61 | color: black; 62 | font-weight: 300; 63 | } 64 | 65 | #logoText{ 66 | font-size: 32px; 67 | font-family: fantasy; 68 | color: white; 69 | } 70 | 71 | #optionsDiv{ 72 | display: flex; 73 | height: auto; 74 | width: auto; 75 | padding: 10px 10px; 76 | } 77 | 78 | #root { 79 | height: 100%; 80 | width: 100%; 81 | } 82 | 83 | 84 | #snap { 85 | object-fit: cover; 86 | border-radius: 50%; 87 | height: 200px; 88 | width: 200px; 89 | margin: 0 auto; 90 | } 91 | 92 | #mainApp #formDiv #stats{ 93 | width: 100%; 94 | height: 100%; 95 | display:flex; 96 | flex-direction: column; 97 | justify-content: center; 98 | align-items: center; 99 | padding: 50px; 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/views/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Form from './components/Form'; 3 | // import '../style/styles.css'; 4 | import Assets from './components/Assets'; 5 | import Visualizations from './components/Visualizations'; 6 | // interface set for class; set type to void because function does not return a value; 7 | interface Vscode { 8 | postMessage(message: any): void; 9 | } 10 | 11 | declare const vscode: Vscode; 12 | 13 | 14 | interface Asset { 15 | name: string; 16 | size: number; 17 | chunks: number[]; 18 | chunkNames: string[]; 19 | } 20 | 21 | interface State { 22 | initialBundleComplete: boolean; 23 | initialBundleStats?: Asset[]; 24 | bundleButtonClicked : boolean; 25 | entry: string; 26 | optimizeButtonClicked: boolean; 27 | postBundleComplete: boolean; 28 | postBundleStats?: Asset[]; 29 | } 30 | 31 | export default class App extends React.Component<{},State> { 32 | constructor(props: any) { 33 | super(props); 34 | this.state= { 35 | initialBundleComplete: false, 36 | initialBundleStats: undefined, 37 | bundleButtonClicked: false, 38 | entry: '', 39 | optimizeButtonClicked: false, 40 | postBundleComplete: false, 41 | postBundleStats: undefined 42 | }; 43 | this.entryHandler = this.entryHandler.bind(this); 44 | } 45 | entryHandler = (event: any) => { 46 | // event.preventdefault(); 47 | console.log('entry: ', this.state.entry); 48 | this.setState({entry: event.target.value}); 49 | } 50 | render() { 51 | let CurrentComponent; 52 | 53 | const runWebpackGetStats = (message : any) => { 54 | console.log ("bundling working"); 55 | this.setState({bundleButtonClicked: true}); 56 | console.log("clicked", this.state.bundleButtonClicked); 57 | return vscode.postMessage(message); 58 | }; 59 | 60 | const optimize = (message:any) => { 61 | console.log("optimizing"); 62 | this.setState({optimizeButtonClicked: true}); 63 | return vscode.postMessage(message); 64 | }; 65 | 66 | const exportFiles = (message: any) => { 67 | console.log('sending to vscode post message with:', message); 68 | return vscode.postMessage(message); 69 | }; 70 | 71 | window.addEventListener('message', event => { 72 | // console.log(event.data) 73 | const message: any = (event.data); 74 | switch (message.command) { 75 | case 'initial': 76 | console.log(JSON.parse(message.field)); 77 | let initialStats: Asset[] = JSON.parse(message.field).assets; 78 | console.log('message recieved', initialStats); 79 | this.setState ({ 80 | initialBundleComplete: true, 81 | initialBundleStats: initialStats 82 | }); 83 | break; 84 | case 'post': 85 | let postStats: Asset[] = JSON.parse(message.field).assets; 86 | this.setState({ 87 | postBundleComplete: true, 88 | postBundleStats: postStats 89 | }); 90 | } 91 | }); 92 | 93 | if (this.state.initialBundleComplete === false) { 94 | CurrentComponent =; 95 | } 96 | if (this.state.bundleButtonClicked) { 97 | CurrentComponent=


98 |
99 | } 100 | if (this.state.initialBundleComplete && this.state.initialBundleStats) { 101 | CurrentComponent = ; 102 | } 103 | 104 | if (this.state.optimizeButtonClicked) { 105 | CurrentComponent =
106 | 107 |
108 | } 109 | 110 | if (this.state.initialBundleStats && this.state.postBundleStats) { 111 | CurrentComponent = ; 112 | } 113 | return ( 114 | 115 |
116 |

snAppy

117 |

118 | {CurrentComponent} 119 |
120 | ); 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /src/views/components/Assets.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | // import '../../style/styles.css' 3 | 4 | interface Asset { 5 | name: string; 6 | size: number; 7 | chunks: number[]; 8 | chunkNames: string[]; 9 | } 10 | 11 | interface Props { 12 | initialBundleStats: Asset[]; 13 | entry: string; 14 | optFunc:any; 15 | } 16 | 17 | export default class Assets extends React.Component { 18 | 19 | constructor(props: any) { 20 | super(props); 21 | } 22 | 23 | 24 | render() { 25 | const {initialBundleStats} = this.props; 26 | return(<> 27 |

Bundled Asset(s): Size

28 |
29 | {initialBundleStats.map((asset:Asset)=>
{`${asset.name}: ${asset.size} KiB`}
)} 30 | 31 |
32 |
33 | 34 | ); 35 | } 36 | } 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/views/components/Form.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | // import '../../style/styles.css'; 3 | 4 | interface State { 5 | css: boolean; 6 | jsx: boolean; 7 | less: boolean; 8 | sass: boolean; 9 | tsx: boolean; 10 | } 11 | 12 | 13 | interface Props { 14 | runFunc: any; 15 | entryFunc: any; 16 | entry: string; 17 | } 18 | export default class Form extends React.Component { 19 | 20 | constructor(props: any) { 21 | super(props); 22 | this.state= { 23 | css: false, 24 | jsx: false, 25 | less: false, 26 | sass: false, 27 | tsx: false 28 | }; 29 | 30 | this.cssHandler = this.cssHandler.bind(this); 31 | this.lessHandler = this.lessHandler.bind(this); 32 | this.jsxHandler = this.jsxHandler.bind(this); 33 | this.sassHandler = this.sassHandler.bind(this); 34 | this.tsxHandler = this.tsxHandler.bind(this); 35 | this.onSubmitForm = this.onSubmitForm.bind(this); 36 | } 37 | 38 | 39 | 40 | cssHandler(event: any) { 41 | console.log('originalcssState', this.state.css); 42 | if (this.state.css === false) { 43 | this.setState({css: true}); 44 | // console.log('css state: ', this.state.css) 45 | } 46 | else { 47 | this.setState({css:false}); 48 | // console.log('css state: ', this.state.css) 49 | 50 | } 51 | } 52 | 53 | lessHandler(event: any) { 54 | console.log('less state: ', this.state.less); 55 | if (this.state.less === false) { 56 | this.setState({less: true}); 57 | // console.log('less state: ', this.state.less) 58 | 59 | } 60 | else { 61 | this.setState({less:false}); 62 | // console.log('less state: ', this.state.less) 63 | 64 | } 65 | } 66 | 67 | jsxHandler(event: any) { 68 | console.log('jsx state: ', this.state.jsx); 69 | 70 | if (this.state.jsx === false) { 71 | this.setState({jsx: true}); 72 | // console.log('jsx state: ', this.state.jsx) 73 | 74 | } 75 | else { 76 | this.setState({jsx:false}); 77 | // console.log('jsx state: ', this.state.jsx) 78 | 79 | } 80 | } 81 | 82 | tsxHandler(event: any) { 83 | console.log('tsx state: ', this.state.tsx); 84 | 85 | if (this.state.tsx === false) { 86 | this.setState({tsx: true}); 87 | // console.log('tsx state: ', this.state.tsx) 88 | 89 | } 90 | else { 91 | this.setState({tsx:false}); 92 | // console.log('tsx state: ', this.state.tsx) 93 | } 94 | } 95 | 96 | sassHandler(event: any) { 97 | console.log('sass state: ', this.state.sass); 98 | if (this.state.sass === false) { 99 | this.setState({sass: true}); 100 | // console.log('sass state: ', this.state.sass) 101 | 102 | } 103 | else { 104 | this.setState({sass:false}); 105 | // console.log('sass state: ', this.state.sass) 106 | 107 | } 108 | } 109 | 110 | onSubmitForm(event: any) { 111 | event.preventDefault(); 112 | // this.props.runFunc(); 113 | 114 | console.log('inside onSubmitFunc'); 115 | 116 | const messageObjectToExtension: any = { 117 | command: 'config', 118 | entry: this.props.entry, 119 | module: { 120 | css: this.state.css, 121 | jsx: this.state.jsx, 122 | tsx: this.state.tsx, 123 | less: this.state.less, 124 | sass: this.state.sass 125 | } 126 | }; 127 | console.log('sending', messageObjectToExtension); 128 | this.props.runFunc(messageObjectToExtension); 129 | } 130 | 131 | 132 | render() { 133 | return( 134 |
135 | 136 | 137 | 138 | 139 |
140 |
141 |

142 |
143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 |

154 |
155 | 156 | 157 | 158 | 159 |
160 | ); 161 | } 162 | 163 | 164 | 165 | 166 | } -------------------------------------------------------------------------------- /src/views/components/Visualizations.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import LiquidGauges from './visuals/LiquidGauges'; 3 | // import '../../style/styles.css' 4 | 5 | interface Asset { 6 | name: string; 7 | size: number; 8 | chunks: number[]; 9 | chunkNames: string[]; 10 | } 11 | 12 | interface Props { 13 | initialBundleStats: Asset[]; 14 | postBundleStats: Asset[]; 15 | exportFunc: any; 16 | } 17 | 18 | interface MainBundle { 19 | initial: number; 20 | post: number; 21 | percentDecrease: number; 22 | } 23 | class Visualizations extends React.Component { 24 | constructor(props: any) { 25 | super(props); 26 | } 27 | render() { 28 | function getMainSize(initial: Asset[], post: Asset[]) { 29 | //iterate thru initial and post - and only use where nane = 'bundle.js' 30 | const output: MainBundle = { 31 | initial: 0, 32 | post: 0, 33 | percentDecrease: 0 34 | }; 35 | for (let obj of initial) { 36 | if (obj.name === 'bundle.js') { 37 | output.initial = obj.size; 38 | } 39 | } 40 | 41 | for (let obj of post) { 42 | if (obj.name === 'bundle.js') { 43 | output.post = obj.size; 44 | } 45 | } 46 | output.percentDecrease = Math.ceil(((output.initial - output.post) / output.initial)* 100); 47 | console.log('output obj', output); 48 | return output; 49 | } 50 | let mainBundle = getMainSize(this.props.initialBundleStats, this.props.postBundleStats); 51 | return ( 52 |
53 |

Stats:

54 |

Before/After of Main Bundle (kiB):

55 | 56 |

{`Initial Bundle: ${mainBundle.initial} | Optimized Bundle: ${mainBundle.post}`}

57 |

{mainBundle.percentDecrease}%

58 |

decrease from initial bundle to optimized bundle

59 |

Assets and Chunks:

60 |
61 | {this.props.postBundleStats.map((asset:Asset)=>
{`${asset.name}: ${asset.size} KiB`}
)} 62 |
63 |
64 | 65 |
66 | ); 67 | } 68 | } 69 | 70 | export default Visualizations; -------------------------------------------------------------------------------- /src/views/components/visuals/LiquidGauges.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {loadLiquidFillGauge, liquidFillGaugeDefaultSettings} from '../../../functions/loadLiquidGauge'; 3 | 4 | interface Asset { 5 | name: string; 6 | size: number; 7 | } 8 | 9 | interface Props { 10 | initialBundleStats: Asset[]; 11 | postBundleStats: Asset[]; 12 | } 13 | 14 | interface MainBundle { 15 | initial: number; 16 | post: number; 17 | } 18 | 19 | const LiquidGauges: React.FC = (props) => { 20 | const [toggle, setToggle] = React.useState(true); 21 | const [bundle, setBundle] = React.useState({initial:0, post:0}); 22 | const [myGauge, setGauge] = React.useState({update(num:number){console.log(num);}}); 23 | React.useEffect(()=>{ 24 | const config = liquidFillGaugeDefaultSettings(); 25 | const mainBundle = getMainSize(props.initialBundleStats, props.postBundleStats); 26 | config.maxValue = mainBundle.initial; 27 | config.circleThickness = 0.05; 28 | config.circleColor = " #4da6ff"; 29 | config.textColor = "#FFFFFF"; 30 | config.waveTextColor = "#004d99"; 31 | config.waveColor = " #66b3ff"; 32 | config.textVertPosition = 0.8; 33 | config.waveAnimateTime = 1000; 34 | config.waveHeight = 0.05; 35 | config.waveAnimate = true; 36 | config.waveRise = false; 37 | config.waveHeightScaling = false; 38 | config.waveOffset = 0.25; 39 | config.textSize = 0.75; 40 | config.waveCount = 3; 41 | const gauge = loadLiquidFillGauge("fillgauge1",mainBundle.initial, config, mainBundle.post); 42 | setGauge(gauge); 43 | setBundle(mainBundle); 44 | }, []); 45 | return ( 46 | { 47 | console.log('on click CLICKED', toggle); 48 | myGauge.update(toggle ? bundle.post : bundle.initial); 49 | setToggle(!toggle);}}> 50 | ); 51 | }; 52 | function getMainSize(initial: Asset[], post: Asset[]) { 53 | //iterate thru initial and post - and only use where nane = 'bundle.js' 54 | let initialN=0; 55 | let postN = 0; 56 | for (let obj of initial) { 57 | if (obj.name === 'bundle.js') { 58 | initialN = obj.size; 59 | } 60 | } 61 | for (let obj of post) { 62 | if (obj.name === 'bundle.js') { 63 | postN = obj.size; 64 | } 65 | } 66 | const mainObjs: MainBundle = { initial: (initialN), post: (postN) }; 67 | console.log('main bundle',mainObjs); 68 | return mainObjs; 69 | } 70 | export default LiquidGauges; -------------------------------------------------------------------------------- /src/views/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | 6 | 7 | ReactDOM.render( 8 | , 9 | document.getElementById('root') 10 | ); -------------------------------------------------------------------------------- /src/views/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "moduleResolution": "node", 5 | "target": "es6", 6 | "outDir": "out", 7 | "lib": [ 8 | "es6", 9 | "dom" 10 | ], 11 | "jsx": "react", 12 | "sourceMap": true, 13 | "rootDir": "..", 14 | "strict": true, 15 | "noUnusedLocals": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "experimentalDecorators": true 19 | }, 20 | "exclude": [ 21 | "node_modules" 22 | ] 23 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6", 8 | "dom" 9 | ], 10 | "jsx": "react", 11 | "sourceMap": true, 12 | "rootDir": "src", 13 | "strict": true /* enable all strict type-checking options */ 14 | /* Additional Checks */ 15 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 16 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 17 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 18 | }, 19 | "exclude": [ 20 | "node_modules", 21 | ".vscode-test", 22 | "**/view" 23 | ] 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [ 9 | true, 10 | "always" 11 | ], 12 | "triple-equals": true 13 | }, 14 | "defaultSeverity": "warning" 15 | } 16 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | 25 | ## Explore the API 26 | 27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 28 | 29 | ## Run tests 30 | 31 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 32 | * Press `F5` to run the tests in a new window with your extension loaded. 33 | * See the output of the test result in the debug console. 34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 36 | * You can create folders inside the `test` folder to structure your tests any way you want. 37 | 38 | ## Go further 39 | 40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 43 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const tsImportPlugin = require('ts-import-plugin'); 3 | 4 | 5 | module.exports = { 6 | /*if we bundle the developer's app, we would ask them for their entry and output: 7 | name: 'their/uri' 8 | path: 'dirname/uri which we can also do by ourselves via the workspace.workspaceFolder (refer to ext.ts) 9 | */ 10 | entry: { 11 | snappy: './src/views/index.tsx' 12 | }, 13 | output: { 14 | path: path.resolve(__dirname, 'out'), 15 | filename: "[name].js" 16 | }, 17 | devtool: 'eval-source-map', 18 | resolve: { 19 | extensions: ['.js', '.ts', '.tsx', '.json'] 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.(ts|tsx)$/, 25 | loader: 'ts-loader', 26 | options: { 27 | getCustomTransformers: () => ({ 28 | before: [ tsImportPlugin({ 29 | libraryName: 'antd', 30 | libraryDirectory: 'es', 31 | style: true, 32 | }) ] 33 | }) 34 | } 35 | }, 36 | { 37 | test: /\.css$/, 38 | use: [ 39 | { 40 | loader: 'style-loader' 41 | }, 42 | { 43 | loader: 'css-loader' 44 | } 45 | ] 46 | } 47 | ] 48 | }, 49 | performance: { 50 | hints: false 51 | } 52 | }; --------------------------------------------------------------------------------