├── .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 |
--------------------------------------------------------------------------------