├── .gitignore ├── tests ├── unit │ ├── input │ │ ├── declared.js │ │ ├── exported.js │ │ └── declared-slots-variable.js │ ├── output │ │ ├── no-semicolon.js │ │ ├── reactive.js │ │ ├── declared.js │ │ ├── exported.js │ │ └── declared-slots-variable.js │ └── reactive.test.js └── app │ ├── .gitignore │ ├── public │ ├── favicon.png │ ├── index.html │ └── global.css │ ├── tsconfig.json │ ├── src │ ├── main.ts │ └── App.svelte │ ├── package.json │ ├── rollup.config.js │ └── README.md ├── cypress.json ├── index.d.ts ├── cypress ├── fixtures │ └── example.json ├── integration │ └── test.spec.js ├── support │ ├── index.js │ └── commands.js └── plugins │ └── index.js ├── jest.config.js ├── stringify.js ├── Readme.md ├── package.json └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | cypress/videos 3 | cypress/screenshots 4 | -------------------------------------------------------------------------------- /tests/unit/input/declared.js: -------------------------------------------------------------------------------- 1 | let triple; 2 | $: triple = count * 3; 3 | -------------------------------------------------------------------------------- /tests/app/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /public/build/ 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /tests/unit/input/exported.js: -------------------------------------------------------------------------------- 1 | export let half; 2 | $: half = count / 2; 3 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:8888/", 3 | "video": false 4 | } 5 | -------------------------------------------------------------------------------- /tests/unit/input/declared-slots-variable.js: -------------------------------------------------------------------------------- 1 | let triple; 2 | $: triple = $$slots.somecontent; 3 | -------------------------------------------------------------------------------- /tests/app/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unlocomqx/svelte-reactive-preprocessor/HEAD/tests/app/public/favicon.png -------------------------------------------------------------------------------- /tests/app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | 4 | "include": ["src/**/*"], 5 | "exclude": ["node_modules/*", "__sapper__/*", "public/*"] 6 | } -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export interface ReactivePreprocessorOptions { 2 | enabled: boolean; 3 | state: boolean; 4 | } 5 | export function reactivePreprocess (options: ReactivePreprocessorOptions); 6 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /tests/app/src/main.ts: -------------------------------------------------------------------------------- 1 | import App from './App.svelte'; 2 | 3 | const app = new App({ 4 | target: document.getElementById('app'), 5 | props: { 6 | name: 'world' 7 | } 8 | }); 9 | 10 | export default app; 11 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property, visit: 3 | * https://jestjs.io/docs/en/configuration.html 4 | */ 5 | 6 | module.exports = { 7 | testMatch: ["**/tests/**/*.test.js"] 8 | }; 9 | -------------------------------------------------------------------------------- /cypress/integration/test.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | describe("App test", () => { 4 | 5 | it("does not break app", () => { 6 | cy 7 | .visit("/") 8 | .get("#app") 9 | .contains("Hello world!") 10 | .get("#add") 11 | .click() 12 | .get("#result") 13 | .should("contain", "Count: 2") 14 | .and("contain", "Double: 4") 15 | .and("contain", "Triple: 6") 16 | .and("contain", "Half: 1") 17 | .and("contain", "Quarter: 0.5"); 18 | }); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /tests/app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/unit/output/no-semicolon.js: -------------------------------------------------------------------------------- 1 | let double; 2 | $: { let svrp_start = Date.now(); let svrp_exec = Math.random(); let start_state = eval("$$self.$capture_state && $$self.$capture_state()"); rpGlobal.rpDsp('SvelteReactiveStart', {statement: "double = count * 2", filename: "", line: 0, id: "ABCD"}, svrp_start, svrp_exec, start_state); double = count * 2; rpGlobal.rpDsp('SvelteReactiveEnd', {statement: "double = count * 2", filename: "", line: 0, id: "ABCD"}, svrp_start, svrp_exec, start_state, eval("$$self.$capture_state && $$self.$capture_state()")); } 3 | var rpGlobal = typeof window !== "undefined" ? window : global; 4 | 5 | rpGlobal.rpDsp = rpGlobal.rpDsp || function() {}; 6 | 7 | rpGlobal.rpDsp('SvelteReactiveEnable', {version: "0.8.2"}); 8 | -------------------------------------------------------------------------------- /tests/unit/output/reactive.js: -------------------------------------------------------------------------------- 1 | let double; 2 | $: { let svrp_start = Date.now(); let svrp_exec = Math.random(); let start_state = eval("$$self.$capture_state && $$self.$capture_state()"); rpGlobal.rpDsp('SvelteReactiveStart', {statement: "double = count * 2;", filename: "", line: 0, id: "ABCD"}, svrp_start, svrp_exec, start_state); double = count * 2; rpGlobal.rpDsp('SvelteReactiveEnd', {statement: "double = count * 2;", filename: "", line: 0, id: "ABCD"}, svrp_start, svrp_exec, start_state, eval("$$self.$capture_state && $$self.$capture_state()")); } 3 | var rpGlobal = typeof window !== "undefined" ? window : global; 4 | 5 | rpGlobal.rpDsp = rpGlobal.rpDsp || function() {}; 6 | 7 | rpGlobal.rpDsp('SvelteReactiveEnable', {version: "0.8.2"}); 8 | -------------------------------------------------------------------------------- /tests/unit/output/declared.js: -------------------------------------------------------------------------------- 1 | let triple; 2 | $: { let svrp_start = Date.now(); let svrp_exec = Math.random(); let start_state = eval("$$self.$capture_state && $$self.$capture_state()"); rpGlobal.rpDsp('SvelteReactiveStart', {statement: "triple = count * 3;", filename: "", line: 0, id: "ABCD"}, svrp_start, svrp_exec, start_state); triple = count * 3; rpGlobal.rpDsp('SvelteReactiveEnd', {statement: "triple = count * 3;", filename: "", line: 0, id: "ABCD"}, svrp_start, svrp_exec, start_state, eval("$$self.$capture_state && $$self.$capture_state()")); } 3 | 4 | var rpGlobal = typeof window !== "undefined" ? window : global; 5 | 6 | rpGlobal.rpDsp = rpGlobal.rpDsp || function() {}; 7 | 8 | rpGlobal.rpDsp('SvelteReactiveEnable', {version: "0.8.2"}); 9 | -------------------------------------------------------------------------------- /tests/unit/output/exported.js: -------------------------------------------------------------------------------- 1 | export let half; 2 | $: { let svrp_start = Date.now(); let svrp_exec = Math.random(); let start_state = eval("$$self.$capture_state && $$self.$capture_state()"); rpGlobal.rpDsp('SvelteReactiveStart', {statement: "half = count / 2;", filename: "", line: 0, id: "ABCD"}, svrp_start, svrp_exec, start_state); half = count / 2; rpGlobal.rpDsp('SvelteReactiveEnd', {statement: "half = count / 2;", filename: "", line: 0, id: "ABCD"}, svrp_start, svrp_exec, start_state, eval("$$self.$capture_state && $$self.$capture_state()")); } 3 | 4 | var rpGlobal = typeof window !== "undefined" ? window : global; 5 | 6 | rpGlobal.rpDsp = rpGlobal.rpDsp || function() {}; 7 | 8 | rpGlobal.rpDsp('SvelteReactiveEnable', {version: "0.8.2"}); 9 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /tests/unit/output/declared-slots-variable.js: -------------------------------------------------------------------------------- 1 | let triple; 2 | $: { let svrp_start = Date.now(); let svrp_exec = Math.random(); let start_state = eval("$$self.$capture_state && $$self.$capture_state()"); rpGlobal.rpDsp('SvelteReactiveStart', {statement: "triple = $$slots.somecontent;", filename: "", line: 0, id: "ABCD"}, svrp_start, svrp_exec, start_state); triple = $$slots.somecontent; rpGlobal.rpDsp('SvelteReactiveEnd', {statement: "triple = $$slots.somecontent;", filename: "", line: 0, id: "ABCD"}, svrp_start, svrp_exec, start_state, eval("$$self.$capture_state && $$self.$capture_state()")); } 3 | 4 | var rpGlobal = typeof window !== "undefined" ? window : global; 5 | 6 | rpGlobal.rpDsp = rpGlobal.rpDsp || function() {}; 7 | 8 | rpGlobal.rpDsp('SvelteReactiveEnable', {version: "0.8.2"}); 9 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | module.exports = (on, config) => { 19 | // `on` is used to hook into various events Cypress emits 20 | // `config` is the resolved Cypress config 21 | } 22 | -------------------------------------------------------------------------------- /tests/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "rollup -c", 6 | "dev": "rollup -c -w", 7 | "start": "sirv public --port 8888", 8 | "validate": "svelte-check" 9 | }, 10 | "devDependencies": { 11 | "@rollup/plugin-commonjs": "^16.0.0", 12 | "@rollup/plugin-node-resolve": "^10.0.0", 13 | "cypress": "^6.2.1", 14 | "rollup": "^2.3.4", 15 | "rollup-plugin-css-only": "^3.1.0", 16 | "rollup-plugin-livereload": "^2.0.0", 17 | "rollup-plugin-svelte": "^7.0.0", 18 | "rollup-plugin-terser": "^7.0.0", 19 | "svelte": "^3.0.0", 20 | "svelte-check": "^1.0.0", 21 | "svelte-preprocess": "^4.0.0", 22 | "@rollup/plugin-typescript": "^6.0.0", 23 | "typescript": "^3.9.3", 24 | "tslib": "^2.0.0", 25 | "@tsconfig/svelte": "^1.0.0" 26 | }, 27 | "dependencies": { 28 | "sirv-cli": "^1.0.0" 29 | } 30 | } -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /stringify.js: -------------------------------------------------------------------------------- 1 | exports = module.exports = stringify 2 | 3 | function stringify(obj, replacer, spaces, cycleReplacer) { 4 | function serializer(replacer, cycleReplacer) { 5 | var stack = [], keys = [] 6 | 7 | if (cycleReplacer == null) cycleReplacer = function(key, value) { 8 | if (stack[0] === value) return "[Circular ~]" 9 | return "[Circular ~." + keys.slice(0, stack.indexOf(value)).join(".") + "]" 10 | } 11 | 12 | return function(key, value) { 13 | if (stack.length > 0) { 14 | var thisPos = stack.indexOf(this) 15 | ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) 16 | ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) 17 | if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value) 18 | } 19 | else stack.push(value) 20 | 21 | return replacer == null ? value : replacer.call(this, key, value) 22 | } 23 | } 24 | 25 | 26 | return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces) 27 | } 28 | -------------------------------------------------------------------------------- /tests/app/public/global.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | color: #333; 9 | margin: 0; 10 | padding: 8px; 11 | box-sizing: border-box; 12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 13 | } 14 | 15 | a { 16 | color: rgb(0,100,200); 17 | text-decoration: none; 18 | } 19 | 20 | a:hover { 21 | text-decoration: underline; 22 | } 23 | 24 | a:visited { 25 | color: rgb(0,80,160); 26 | } 27 | 28 | label { 29 | display: block; 30 | } 31 | 32 | input, button, select, textarea { 33 | font-family: inherit; 34 | font-size: inherit; 35 | -webkit-padding: 0.4em 0; 36 | padding: 0.4em; 37 | margin: 0 0 0.5em 0; 38 | box-sizing: border-box; 39 | border: 1px solid #ccc; 40 | border-radius: 2px; 41 | } 42 | 43 | input:disabled { 44 | color: #ccc; 45 | } 46 | 47 | button { 48 | color: #333; 49 | background-color: #f4f4f4; 50 | outline: none; 51 | } 52 | 53 | button:disabled { 54 | color: #999; 55 | } 56 | 57 | button:not(:disabled):active { 58 | background-color: #ddd; 59 | } 60 | 61 | button:focus { 62 | border-color: #666; 63 | } 64 | -------------------------------------------------------------------------------- /tests/app/src/App.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |
23 |

Hello {name}!

24 |
25 | 26 |
27 |
28 | Count: {count} 29 |
30 |
31 | Double: {double} 32 |
33 |
34 | Triple: {triple} 35 |
36 |
37 | Half: {half} 38 |
39 |
40 | Quarter: {quarter} 41 |
42 |
43 |
44 |
45 | 46 | 67 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | [](https://www.npmjs.com/package/svelte-reactive-preprocessor) 2 | 3 | # Svelte Reactive Preprocessor 4 | Wrap svelte reactive statements with custom events to allow devtools to detect them 5 | 6 | ## How to install 7 | ````shell 8 | npm i -D svelte-reactive-preprocessor 9 | ```` 10 | 11 | ## How to use 12 | First import the package like this 13 | ```javascript 14 | const { reactivePreprocess } = require("svelte-reactive-preprocessor"); 15 | ``` 16 | 17 | Then in the svelte loader options, add the reactive preprocessor like this 18 | ```javascript 19 | plugins: [ 20 | svelte({ 21 | preprocess: reactivePreprocess(), 22 | } 23 | ], 24 | ``` 25 | 26 | If you are already using another preprocessor, add the reactive preprocessor like this 27 | ```javascript 28 | preprocess: [ 29 | sveltePreprocess(), 30 | reactivePreprocess(), 31 | ], 32 | ``` 33 | 34 | ## Options 35 | The preprocessor options are listed below with their default values 36 | 37 | ```javascript 38 | reactivePreprocess({ 39 | enabled: true, 40 | state: true, 41 | }) 42 | ``` 43 | 44 | ### enabled: boolean 45 | Enable or disable the preprocessor 46 | 47 | ### state: boolean 48 | Whether to send the state to devtools. Set to false if you encounter performance issues. 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-reactive-preprocessor", 3 | "version": "0.8.3", 4 | "description": "Wrap svelte reactive statements with custom events to allow devtools to detect them", 5 | "main": "index.js", 6 | "scripts": { 7 | "install-deps": "npm install --prefix tests/app", 8 | "start-server": "npm run dev --prefix tests/app", 9 | "test:jest": "jest", 10 | "test:cypress": "cypress run", 11 | "test": "npm run install-deps && npm run test:jest && start-server-and-test start-server http://localhost:8888 test:cypress", 12 | "patch": "npm run test && npm version patch && npm publish && git push", 13 | "minor": "npm run test && npm version minor && npm publish && git push" 14 | }, 15 | "keywords": [ 16 | "svelte", 17 | "reactive", 18 | "debugger" 19 | ], 20 | "author": "unlocomqx", 21 | "license": "ISC", 22 | "dependencies": { 23 | "acorn": "^8.0.4", 24 | "escape-string-regexp": "^4.0.0", 25 | "json-stringify-safe": "^5.0.1", 26 | "linenumber": "^1.0.1", 27 | "periscopic": "^2.0.3" 28 | }, 29 | "devDependencies": { 30 | "cypress": "^6.2.1", 31 | "jest": "^26.6.3", 32 | "jest-diff": "^26.6.2", 33 | "start-server-and-test": "^1.11.7" 34 | }, 35 | "types": "./index.d.ts", 36 | "directories": { 37 | "test": "tests" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/unlocomqx/svelte-reactive-preprocessor.git" 42 | }, 43 | "bugs": { 44 | "url": "https://github.com/unlocomqx/svelte-reactive-preprocessor/issues" 45 | }, 46 | "homepage": "https://github.com/unlocomqx/svelte-reactive-preprocessor#readme" 47 | } 48 | -------------------------------------------------------------------------------- /tests/unit/reactive.test.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | let {reactivePreprocess} = require("../../index"); 4 | const {diffStringsUnified} = require("jest-diff"); 5 | 6 | function transform(content) { 7 | let transformer = reactivePreprocess(); 8 | let {code} = transformer.script.call(null, {content, id: "ABCD", filename: "", line: 0}); 9 | return code; 10 | } 11 | 12 | function read(file) { 13 | return fs.readFileSync(path.join(__dirname, file)).toString(); 14 | } 15 | 16 | test("transform reactive statement and add explicit let", function () { 17 | // assert.equal(transfrorm("$: double = count * 2;"), read("output/reactive.js").trim()); 18 | let transformed = transform("$: double = count * 2;"); 19 | let expected = read("output/reactive.js").trim(); 20 | 21 | console.log(diffStringsUnified(transformed, expected)); 22 | 23 | expect(transformed).toContain(expected); 24 | }); 25 | 26 | test("transform reactive statement and does not add explicit let for declared variable", function () { 27 | let transformed = transform(read("input/declared.js")); 28 | let expected = read("output/declared.js").trim(); 29 | 30 | console.log(diffStringsUnified(transformed, expected)); 31 | 32 | expect(expected).toContain(expected); 33 | }); 34 | 35 | test("transform reactive statement and does not add explicit let for exported variable", function () { 36 | let transformed = transform(read("input/exported.js")); 37 | let expected = read("output/exported.js").trim(); 38 | 39 | console.log(diffStringsUnified(transformed, expected)); 40 | 41 | expect(transformed).toContain(expected); 42 | }); 43 | 44 | test("transform statement not terminated by semiclon", function () { 45 | let transformed = transform("$: double = count * 2"); 46 | let expected = read("output/no-semicolon.js").trim(); 47 | 48 | console.log(diffStringsUnified(transformed, expected)); 49 | 50 | expect(transformed).toContain(expected); 51 | }); 52 | 53 | test("transform reactive statement and keep $$", function () { 54 | let transformed = transform(read("input/declared-slots-variable.js")); 55 | let expected = read("output/declared-slots-variable.js").trim(); 56 | 57 | console.log(diffStringsUnified(transformed, expected)); 58 | 59 | expect(expected).toContain(expected); 60 | }); -------------------------------------------------------------------------------- /tests/app/rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from "rollup-plugin-svelte" 2 | import commonjs from "@rollup/plugin-commonjs" 3 | import resolve from "@rollup/plugin-node-resolve" 4 | import livereload from "rollup-plugin-livereload" 5 | import { terser } from "rollup-plugin-terser" 6 | import sveltePreprocess from "svelte-preprocess" 7 | 8 | import typescript from "@rollup/plugin-typescript" 9 | import css from "rollup-plugin-css-only" 10 | 11 | const { reactivePreprocess } = require("../../index"); 12 | 13 | const production = !process.env.ROLLUP_WATCH 14 | 15 | function serve() { 16 | let server 17 | 18 | function toExit() { 19 | if (server) { 20 | server.kill(0) 21 | } 22 | } 23 | 24 | return { 25 | writeBundle() { 26 | if (server) { 27 | return 28 | } 29 | server = require("child_process").spawn("npm", ["run", "start", "--", "--dev"], { 30 | stdio: ["ignore", "inherit", "inherit"], 31 | shell: true 32 | }) 33 | 34 | process.on("SIGTERM", toExit) 35 | process.on("exit", toExit) 36 | } 37 | } 38 | } 39 | 40 | export default { 41 | input: "src/main.ts", 42 | output: { 43 | sourcemap: true, 44 | format: "iife", 45 | name: "app", 46 | file: "public/build/bundle.js" 47 | }, 48 | plugins: [ 49 | svelte({ 50 | preprocess: [ 51 | sveltePreprocess(), 52 | reactivePreprocess(), 53 | ], 54 | compilerOptions: { 55 | // enable run-time checks when not in production 56 | dev: !production 57 | } 58 | }), 59 | // we'll extract any component CSS out into 60 | // a separate file - better for performance 61 | css({output: "bundle.css"}), 62 | 63 | // If you have external dependencies installed from 64 | // npm, you'll most likely need these plugins. In 65 | // some cases you'll need additional configuration - 66 | // consult the documentation for details: 67 | // https://github.com/rollup/plugins/tree/master/packages/commonjs 68 | resolve({ 69 | browser: true, 70 | dedupe: ["svelte"] 71 | }), 72 | commonjs(), 73 | typescript({ 74 | sourceMap: !production, 75 | inlineSources: !production 76 | }), 77 | 78 | // In dev mode, call `npm run start` once 79 | // the bundle has been generated 80 | !production && serve(), 81 | 82 | // Watch the `public` directory and refresh the 83 | // browser on changes when not in production 84 | !production && livereload("public"), 85 | 86 | // If we're building for production (npm run build 87 | // instead of npm run dev), minify 88 | production && terser() 89 | ], 90 | watch: { 91 | clearScreen: false 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /tests/app/README.md: -------------------------------------------------------------------------------- 1 | *Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)* 2 | 3 | --- 4 | 5 | # svelte app 6 | 7 | This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template. 8 | 9 | To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit): 10 | 11 | ```bash 12 | npx degit sveltejs/template svelte-app 13 | cd svelte-app 14 | ``` 15 | 16 | *Note that you will need to have [Node.js](https://nodejs.org) installed.* 17 | 18 | 19 | ## Get started 20 | 21 | Install the dependencies... 22 | 23 | ```bash 24 | cd svelte-app 25 | npm install 26 | ``` 27 | 28 | ...then start [Rollup](https://rollupjs.org): 29 | 30 | ```bash 31 | npm run dev 32 | ``` 33 | 34 | Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes. 35 | 36 | By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`. 37 | 38 | If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense. 39 | 40 | ## Building and running in production mode 41 | 42 | To create an optimised version of the app: 43 | 44 | ```bash 45 | npm run build 46 | ``` 47 | 48 | You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com). 49 | 50 | 51 | ## Single-page app mode 52 | 53 | By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere. 54 | 55 | If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json: 56 | 57 | ```js 58 | "start": "sirv public --single" 59 | ``` 60 | 61 | ## Using TypeScript 62 | 63 | This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with: 64 | 65 | ```bash 66 | node scripts/setupTypeScript.js 67 | ``` 68 | 69 | Or remove the script via: 70 | 71 | ```bash 72 | rm scripts/setupTypeScript.js 73 | ``` 74 | 75 | ## Deploying to the web 76 | 77 | ### With [Vercel](https://vercel.com) 78 | 79 | Install `vercel` if you haven't already: 80 | 81 | ```bash 82 | npm install -g vercel 83 | ``` 84 | 85 | Then, from within your project folder: 86 | 87 | ```bash 88 | cd public 89 | vercel deploy --name my-project 90 | ``` 91 | 92 | ### With [surge](https://surge.sh/) 93 | 94 | Install `surge` if you haven't already: 95 | 96 | ```bash 97 | npm install -g surge 98 | ``` 99 | 100 | Then, from within your project folder: 101 | 102 | ```bash 103 | npm run build 104 | surge public my-project.surge.sh 105 | ``` 106 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const acorn = require("acorn") 2 | const {extract_names, analyze} = require("periscopic") 3 | const linenumber = require("linenumber") 4 | const escapeStringRegexp = require("escape-string-regexp") 5 | const stringify = require("./stringify") 6 | const fs = require("fs") 7 | 8 | function inNodeModules(path) { 9 | return /\/node_modules\//.test(path) 10 | } 11 | 12 | let options = { 13 | enabled: true, 14 | state: true, 15 | } 16 | 17 | function doPreprocess(params) { 18 | if (inNodeModules(params.filename)) { 19 | return 20 | } 21 | let code = params.content 22 | const file_contents = fs.existsSync(params.filename) ? fs.readFileSync(params.filename).toString() : null 23 | 24 | const replacements = [] 25 | const inject_vars = new Set() 26 | 27 | let parsed 28 | try { 29 | parsed = acorn.parse(code, {ecmaVersion: "latest", sourceType: "module"}) 30 | } catch (e) { 31 | console.warn("An error occurred in the svelte-reactive-preprocessor, make sure it's placed after the typescript preprocessor: " + e.message) 32 | return 33 | } 34 | 35 | function getLineNumber(labeled_statement) { 36 | if (!file_contents) { 37 | return 0 38 | } 39 | let result = linenumber(file_contents, escapeStringRegexp(labeled_statement)) 40 | if (!result) { 41 | return 0 42 | } 43 | return result[0].line 44 | } 45 | 46 | let state_eval = options.state ? "$$self.$capture_state && $$self.$capture_state()" : "{}" 47 | 48 | function wrapStatement(statement, filename, line_number) { 49 | // options.id comes from unit tests only 50 | const id = params.id || uniqId(4) 51 | let details = `{statement: ${stringify(statement)}, filename: ${stringify(filename)}, line: ${line_number}, id: "${id}"}` 52 | let start_ev = `{ let svrp_start = Date.now(); let svrp_exec = Math.random(); let start_state = eval("${state_eval}"); rpGlobal.rpDsp('SvelteReactiveStart', ${details}, svrp_start, svrp_exec, start_state);` 53 | // eval is used to avoid the svelte compiler. 54 | let end_ev = `rpGlobal.rpDsp('SvelteReactiveEnd', ${details}, svrp_start, svrp_exec, start_state, eval("${state_eval}")); }` 55 | 56 | const semicolon = /;$/.test(statement) ? "" : ";" 57 | return `${start_ev} ${statement}${semicolon} ${end_ev}` 58 | } 59 | 60 | function replaceRange(str, start, end, substitute) { 61 | return str.substring(0, start) + substitute + str.substring(end) 62 | } 63 | 64 | function uniqId(length) { 65 | var result = "" 66 | var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 67 | var charactersLength = characters.length 68 | for (var i = 0; i < length; i++) { 69 | result += characters.charAt(Math.floor(Math.random() * charactersLength)) 70 | } 71 | return result 72 | } 73 | 74 | function addReactiveStatement(node) { 75 | const body = node.body 76 | 77 | if (body.type === "ExpressionStatement") { 78 | const {expression} = node.body 79 | if (expression.type === "AssignmentExpression") { 80 | if (expression.left.type !== "MemberExpression") { 81 | extract_names(expression.left).forEach(name => { 82 | if (name[0] !== "$") { 83 | inject_vars.add(name) 84 | } 85 | }) 86 | } 87 | } 88 | } 89 | 90 | const labeled_statement = code.substring(node.start, node.end) 91 | const statement = code.substring(body.start, body.end) 92 | const wrapped = wrapStatement(statement, params.filename, getLineNumber(labeled_statement)) 93 | const uniqid = uniqId(statement.length) 94 | code = replaceRange(code, body.start, body.end, uniqid) 95 | replacements.push({ 96 | uniqid, 97 | statement: wrapped, 98 | }) 99 | } 100 | 101 | function replaceReactiveStatements() { 102 | replacements.forEach(repl => { 103 | code = code.replace(repl.uniqid, () => repl.statement) 104 | }) 105 | } 106 | 107 | function injectVariables(declarations) { 108 | inject_vars.forEach(variable => { 109 | if (!declarations.has(variable)) { 110 | code = `let ${variable};\n` + code 111 | } 112 | }) 113 | } 114 | 115 | if (parsed && parsed.body) { 116 | const {scope} = analyze(parsed) 117 | 118 | parsed.body.forEach(node => { 119 | if (node.type === "LabeledStatement" && node.label.name === "$") { 120 | addReactiveStatement(node) 121 | } 122 | }) 123 | 124 | if (replacements.length) { 125 | replaceReactiveStatements() 126 | } 127 | 128 | injectVariables(scope.declarations) 129 | } 130 | 131 | code += `\nvar rpGlobal = typeof window !== "undefined" ? window : global;\n` 132 | code += `\nrpGlobal.rpDsp = rpGlobal.rpDsp || function() {};\n` 133 | 134 | const version = require("./package.json").version 135 | code += `\nrpGlobal.rpDsp('SvelteReactiveEnable', {version: "${version}"});` 136 | 137 | return {code} 138 | } 139 | 140 | function reactivePreprocess(userOptions) { 141 | options = { 142 | ...options, 143 | ...userOptions 144 | } 145 | 146 | if (!options.enabled) { 147 | return {} 148 | } 149 | 150 | return { 151 | script: doPreprocess 152 | } 153 | } 154 | 155 | module.exports = {reactivePreprocess} 156 | --------------------------------------------------------------------------------