├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── 0.rfc.yml │ ├── 1.bug.yml │ ├── 2.example.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── SECURITY.md └── workflows │ ├── test-examples.yml │ └── test-package.yml ├── .gitignore ├── .gitmodules ├── .npmrc ├── .prettierrc.mjs ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── api ├── messaging │ ├── .gitignore │ ├── LICENSE │ ├── jest.config.mjs │ ├── package.json │ ├── src │ │ ├── background.ts │ │ ├── hook.ts │ │ ├── index.ts │ │ ├── message.ts │ │ ├── port.ts │ │ ├── pub-sub.ts │ │ ├── relay.test.ts │ │ ├── relay.ts │ │ ├── types.ts │ │ └── utils.ts │ └── tsconfig.json ├── persistent │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── background.ts │ │ └── index.ts │ ├── tsconfig.json │ └── tsup.config.ts └── selector │ ├── .gitignore │ ├── LICENSE │ ├── package.json │ ├── src │ ├── background.ts │ ├── hook.ts │ ├── index.ts │ ├── monitor.ts │ └── types.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── cli ├── create-plasmo │ ├── bin │ │ └── index.mjs │ ├── package.json │ ├── src │ │ ├── commands.ts │ │ └── index.ts │ └── tsconfig.json └── plasmo │ ├── .eslintrc.js │ ├── LICENSE │ ├── README.md │ ├── bin │ └── index.mjs │ ├── i18n │ ├── README.de-DE.md │ ├── README.fr-FR.md │ ├── README.id-ID.md │ ├── README.ja-JP.md │ ├── README.ko-KR.md │ ├── README.ru-RU.md │ ├── README.tr-TR.md │ ├── README.vi-VN.md │ └── README.zh-CN.md │ ├── index.mjs │ ├── package.json │ ├── src │ ├── commands │ │ ├── build.ts │ │ ├── dev.ts │ │ ├── help.ts │ │ ├── index.ts │ │ ├── init.ts │ │ ├── package.ts │ │ ├── start.ts │ │ └── version.ts │ ├── features │ │ ├── background-service-worker │ │ │ ├── bgsw-entry.ts │ │ │ ├── bgsw-main-world-script.ts │ │ │ ├── bgsw-messaging-declaration.ts │ │ │ ├── bgsw-messaging.ts │ │ │ └── update-bgsw-entry.ts │ │ ├── env │ │ │ ├── env-config.ts │ │ │ └── env-declaration.ts │ │ ├── extension-devtools │ │ │ ├── common-path.ts │ │ │ ├── content-script-config.ts │ │ │ ├── generate-icons.ts │ │ │ ├── get-bundle-config.ts │ │ │ ├── git-ignore.ts │ │ │ ├── package-file.ts │ │ │ ├── parse-ast.ts │ │ │ ├── project-path.ts │ │ │ ├── project-watcher.ts │ │ │ ├── strip-underscore.ts │ │ │ ├── template-path.ts │ │ │ └── tsconfig.ts │ │ ├── extra │ │ │ ├── cache-busting.ts │ │ │ └── next-new-tab.ts │ │ ├── framework-update │ │ │ └── version-tracker.ts │ │ ├── helpers │ │ │ ├── create-parcel-bundler.ts │ │ │ ├── crypto.ts │ │ │ ├── flag.ts │ │ │ ├── loading-animation.ts │ │ │ ├── package-manager.ts │ │ │ ├── print.ts │ │ │ ├── prompt.ts │ │ │ └── traverse.ts │ │ ├── manifest-factory │ │ │ ├── base.ts │ │ │ ├── create-manifest.ts │ │ │ ├── mv2.ts │ │ │ ├── mv3.ts │ │ │ ├── scaffolder.ts │ │ │ ├── ui-library.ts │ │ │ └── zip.ts │ │ └── project-creator │ │ │ ├── from-existing-manifest.ts │ │ │ ├── get-raw-name.ts │ │ │ ├── git-init.ts │ │ │ ├── index.ts │ │ │ ├── install-dependencies.ts │ │ │ └── print-ready.ts │ ├── index.ts │ └── type.ts │ ├── templates │ ├── plasmo.d.ts │ ├── static │ │ ├── background │ │ │ └── index.ts │ │ ├── common │ │ │ ├── csui-container-react.tsx │ │ │ ├── csui-container-vanilla.tsx │ │ │ ├── csui.ts │ │ │ ├── react.ts │ │ │ └── vue.ts │ │ ├── react17 │ │ │ ├── content-script-ui-mount.tsx │ │ │ ├── index.html │ │ │ └── index.tsx │ │ ├── react18 │ │ │ ├── content-script-ui-mount.tsx │ │ │ ├── index.html │ │ │ └── index.tsx │ │ ├── react19 │ │ │ ├── content-script-ui-mount.tsx │ │ │ ├── index.html │ │ │ └── index.tsx │ │ ├── svelte4 │ │ │ ├── content-script-ui-mount.ts │ │ │ ├── index.html │ │ │ └── index.ts │ │ ├── vanilla │ │ │ ├── index.html │ │ │ └── index.ts │ │ └── vue3 │ │ │ ├── content-script-ui-mount.ts │ │ │ ├── index.html │ │ │ └── index.ts │ └── tsconfig.base.json │ └── tsconfig.json ├── core ├── parcel-bundler │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── bit-set.ts │ │ ├── can-merge.ts │ │ ├── create-bundle.ts │ │ ├── create-ideal-graph.ts │ │ ├── decorate-legacy-graph.ts │ │ ├── get-entry-by-target.ts │ │ ├── get-reachable-bundle-root.ts │ │ ├── index.ts │ │ ├── remove-bundle.ts │ │ └── types.ts │ └── tsconfig.json ├── parcel-compressor-utf8 │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── utf8-transform.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── parcel-config │ ├── .gitignore │ ├── index.json │ └── package.json ├── parcel-core │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── resolve-options.ts │ │ └── types.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── parcel-namer-manifest │ ├── .gitignore │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── parcel-optimizer-encapsulate │ ├── .gitignore │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── parcel-optimizer-es │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── blob-to-string.ts │ │ └── index.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── parcel-packager │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── get-web-accessible-resources.ts │ │ ├── index.ts │ │ └── utils.ts │ └── tsconfig.json ├── parcel-resolver-post │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── handle-hacks.ts │ │ ├── handle-module-exports.ts │ │ ├── handle-ts-path.ts │ │ ├── index.ts │ │ ├── shared.ts │ │ └── utils.ts │ └── tsconfig.json ├── parcel-resolver │ ├── .gitignore │ ├── index.mjs │ ├── package.json │ ├── src │ │ ├── dev-polyfills │ │ │ ├── react-refresh.ts │ │ │ └── react-refresh │ │ │ │ └── runtime.ts │ │ ├── handle-absolute-root.ts │ │ ├── handle-alias.ts │ │ ├── handle-plasmo-internal.ts │ │ ├── handle-polyfill.ts │ │ ├── handle-remote-caching.ts │ │ ├── handle-tilde-src.ts │ │ ├── index.ts │ │ ├── polyfills │ │ │ ├── assert.ts │ │ │ ├── buffer.ts │ │ │ ├── console.ts │ │ │ ├── constants.ts │ │ │ ├── crc-32.ts │ │ │ ├── crc-32 │ │ │ │ └── crc32c.ts │ │ │ ├── crypto.ts │ │ │ ├── domain.ts │ │ │ ├── events.ts │ │ │ ├── http.ts │ │ │ ├── https.ts │ │ │ ├── os.ts │ │ │ ├── path.ts │ │ │ ├── process.ts │ │ │ ├── punycode.ts │ │ │ ├── querystring.ts │ │ │ ├── stream.ts │ │ │ ├── string_decoder.ts │ │ │ ├── sys.ts │ │ │ ├── timers.ts │ │ │ ├── tty.ts │ │ │ ├── url.ts │ │ │ ├── util.ts │ │ │ ├── vm.ts │ │ │ └── zlib.ts │ │ └── shared.ts │ └── tsconfig.json ├── parcel-runtime │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── runtimes │ │ │ ├── background-service-runtime.ts │ │ │ ├── page-runtime.ts │ │ │ └── script-runtime.ts │ │ ├── types.ts │ │ └── utils │ │ │ ├── 0-patch-module.ts │ │ │ ├── bgsw.ts │ │ │ ├── hmr-check.ts │ │ │ ├── hmr-utils.ts │ │ │ ├── inject-socket.ts │ │ │ ├── loading-indicator.ts │ │ │ └── react-refresh.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── parcel-transformer-inject-env │ ├── .gitignore │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── parcel-transformer-inline-css │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── get-tagets.ts │ │ └── index.ts │ └── tsconfig.json ├── parcel-transformer-lab │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── state.ts │ └── tsconfig.json ├── parcel-transformer-manifest │ ├── .gitignore │ ├── package.json │ ├── runtime │ │ └── plasmo-default-background.ts │ ├── src │ │ ├── csp-patch-hmr.ts │ │ ├── handle-action.ts │ │ ├── handle-background.ts │ │ ├── handle-content-scripts.ts │ │ ├── handle-declarative-net-request.ts │ │ ├── handle-deep-loc.ts │ │ ├── handle-dictionaries.ts │ │ ├── handle-locales.ts │ │ ├── handle-sandboxes.ts │ │ ├── handle-tabs.ts │ │ ├── index.ts │ │ ├── normalize-manifest.ts │ │ ├── schema.ts │ │ ├── state.ts │ │ ├── utils.ts │ │ └── validate-version.ts │ └── tsconfig.json ├── parcel-transformer-svelte │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── convert-error.ts │ │ ├── convert-loc.ts │ │ ├── index.ts │ │ ├── source-map.ts │ │ └── types.ts │ └── tsconfig.json └── parcel-transformer-vue │ ├── .gitignore │ ├── package.json │ ├── src │ └── index.ts │ └── tsconfig.json ├── eslint.config.mjs ├── package.json ├── packages ├── framework-shared │ ├── build-socket │ │ ├── event.ts │ │ └── index.ts │ ├── package.json │ └── tsconfig.json └── init │ ├── .gitignore │ ├── bpp.yml │ ├── entries │ ├── background.ts │ ├── content.ts │ ├── contents │ │ ├── inline.tsx │ │ └── overlay.tsx │ ├── newtab.tsx │ ├── options.tsx │ └── popup.tsx │ ├── index.json │ ├── package.json │ ├── templates │ ├── README.md │ ├── assets │ │ └── icon.png │ └── tsconfig.json │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── renovate.json ├── scripts └── move-prettier-cjs-to-mjs.bash └── turbo.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: PlasmoHQ 4 | 5 | # patreon: # Replace with a single Patreon username 6 | # open_collective: # Replace with a single Open Collective username 7 | # ko_fi: # Replace with a single Ko-fi username 8 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 9 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 10 | # liberapay: # Replace with a single Liberapay username 11 | # issuehunt: # Replace with a single IssueHunt username 12 | # otechie: # Replace with a single Otechie username 13 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 14 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2.example.yml: -------------------------------------------------------------------------------- 1 | name: 📓 Request/Improve an Example 2 | description: Request or Improve a Plasmo Framework with-* example 3 | title: "[EXP] " 4 | labels: ["documentation"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **Thank you for taking the time to fill out this example request!** 🥳 10 | 11 | - type: textarea 12 | attributes: 13 | label: What is the example you wish to see? 14 | description: "Example: I would like to see more examples of how to use `X-Framework`." 15 | validations: 16 | required: true 17 | 18 | - type: textarea 19 | attributes: 20 | label: Is there any context that might help us understand? 21 | description: A clear description of any added context that might help us understand. 22 | validations: 23 | required: false 24 | 25 | - type: checkboxes 26 | id: terms 27 | attributes: 28 | label: Code of Conduct 29 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md). 30 | options: 31 | - label: I agree to follow this project's Code of Conduct 32 | required: true 33 | - label: I checked the [current issues](https://github.com/PlasmoHQ/plasmo/issues) for duplicate problems. 34 | required: true 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Join our Discord server 4 | url: https://www.plasmo.com/s/d 5 | about: Ask questions and discuss with other community members 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ## Details 7 | 8 | This PR ... 9 | 10 | ### Code of Conduct 11 | 12 | - [ ] I agree to follow this project's [Code of Conduct](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md) 13 | - [ ] I agree to license this contribution under the MIT LICENSE 14 | - [ ] I checked the [current PR](https://github.com/PlasmoHQ/plasmo/pulls) for duplication. 15 | 16 | ## Contacts 17 | 18 | - (OPTIONAL) Discord ID: 19 | 20 | If your PR is accepted, we will award you with the `Contributor` role on Discord server. 21 | 22 | To join the server, visit: https://www.plasmo.com/s/d 23 | -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 | ../cli/plasmo/README.md 2 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | Contact: security@plasmo.com 2 | Expires: 2100-01-01T00:00:00.000Z 3 | Acknowledgments: https://www.plasmo.com/security/hall-of-fame 4 | -------------------------------------------------------------------------------- /.github/workflows/test-examples.yml: -------------------------------------------------------------------------------- 1 | name: Test examples 2 | on: 3 | push: 4 | branches: ["main"] 5 | pull_request: 6 | types: [opened, synchronize] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [20.x] 15 | steps: 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - uses: actions/checkout@master 20 | with: 21 | submodules: "recursive" 22 | - name: Setup pnpm 23 | uses: pnpm/action-setup@v4.1.0 24 | with: 25 | run_install: false 26 | 27 | - name: Get pnpm store directory 28 | id: pnpm-cache 29 | shell: bash 30 | run: | 31 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 32 | 33 | - uses: actions/cache@v4.2.3 34 | name: Setup pnpm cache 35 | with: 36 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 37 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 38 | restore-keys: | 39 | ${{ runner.os }}-pnpm-store- 40 | 41 | - name: "Build local version of Plasmo" 42 | run: | 43 | pnpm i --no-frozen-lockfile 44 | pnpm run build:packages 45 | pnpm run build:api 46 | pnpm i 47 | pnpm run build:cli 48 | pnpm i 49 | 50 | - name: "Build all examples" 51 | run: pnpm run build:examples 52 | 53 | - name: "Test all examples" 54 | run: pnpm run test:examples 55 | 56 | -------------------------------------------------------------------------------- /.github/workflows/test-package.yml: -------------------------------------------------------------------------------- 1 | name: Test package manager execution 2 | on: 3 | release: 4 | types: [published] 5 | workflow_dispatch: 6 | inputs: 7 | tag: 8 | description: "Release tag to test, i.e latest, 1.0.0. Default latest" 9 | required: false 10 | default: latest 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [20.x] 19 | package-manager: 20 | [{ name: "pnpm", exec: "pnpm dlx" }, { name: "npm", exec: "npx -y" }] 21 | steps: 22 | - uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - name: "Install package manager" 26 | run: npm install -g ${{ matrix.package-manager.name }} 27 | - name: Check if Plasmo command works 28 | run: ${{ matrix.package-manager.exec }} plasmo@${{ github.event.inputs.tag }} version 29 | - name: Check if plasmo init works at all 30 | run: yes "lab" | ${{ matrix.package-manager.exec }} plasmo@${{ github.event.inputs.tag }} init --verbose 31 | - name: Check if building is possible and if it built 32 | run: | 33 | pushd lab 34 | ${{ matrix.package-manager.name }} run build 35 | pushd build 36 | popd 37 | timeout 10 ${{ matrix.package-manager.name }} run dev || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | dist/ 16 | 17 | # electron bundle 18 | **/resources/app 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | .idea 24 | 25 | # debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | .pnpm-debug.log* 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # turbo 35 | .turbo 36 | 37 | .tsbuildinfo 38 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "packages/rps"] 2 | path = packages/rps 3 | url = https://github.com/PlasmoHQ/rps.git 4 | branch = main 5 | [submodule "packages/puro"] 6 | path = packages/puro 7 | url = https://github.com/PlasmoHQ/puro.git 8 | branch = main 9 | [submodule "packages/gcp-refresh-token"] 10 | path = packages/gcp-refresh-token 11 | url = https://github.com/PlasmoHQ/gcp-refresh-token.git 12 | branch = main 13 | [submodule "packages/use-hashed-state"] 14 | path = packages/use-hashed-state 15 | url = https://github.com/PlasmoHQ/use-hashed-state.git 16 | branch = main 17 | [submodule "examples"] 18 | path = examples 19 | url = https://github.com/PlasmoHQ/examples.git 20 | [submodule "packages/permission-ui"] 21 | path = packages/permission-ui 22 | url = https://github.com/PlasmoHQ/permission-ui.git 23 | [submodule "packages/utils"] 24 | path = packages/utils 25 | url = https://github.com/PlasmoHQ/plasmo-utils.git 26 | [submodule "packages/constants"] 27 | path = packages/constants 28 | url = https://github.com/PlasmoHQ/plasmo-constants.git 29 | [submodule "packages/config"] 30 | path = packages/config 31 | url = https://github.com/PlasmoHQ/plasmo-config.git 32 | [submodule "api/storage"] 33 | path = api/storage 34 | url = https://github.com/PlasmoHQ/storage.git 35 | branch = main 36 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-workspace-protocol = true 2 | prefer-workspace-packages = true 3 | save-exact = true 4 | link-workspace-packages = true 5 | strict-peer-dependencies = false 6 | git-tag-version = false 7 | commit-hooks = false -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Options} 3 | */ 4 | export default { 5 | printWidth: 80, 6 | tabWidth: 2, 7 | useTabs: false, 8 | semi: false, 9 | singleQuote: false, 10 | trailingComma: "none", 11 | bracketSpacing: true, 12 | bracketSameLine: true, 13 | plugins: ["@ianvs/prettier-plugin-sort-imports"], 14 | importOrder: [ 15 | "", // Node.js built-in modules 16 | "", // Imports not matched by other special words or groups. 17 | "", // Empty line 18 | "^@plasmo/(.*)$", 19 | "", 20 | "^@plasmohq/(.*)$", 21 | "", 22 | "^@plasmo-static-common/(.*)$", 23 | "", 24 | "^~(.*)$", 25 | "", 26 | "^[./]", 27 | "", 28 | "__plasmo_import_module__", 29 | "__plasmo_mount_content_script__" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.svn": true, 6 | "**/.hg": true, 7 | "**/CVS": true, 8 | "**/.DS_Store": true, 9 | "**/Thumbs.db": true, 10 | "**/node_modules": true, 11 | "**/.turbo": true, 12 | "**/.next": true, 13 | "**/*.log": true 14 | }, 15 | "[svg]": { 16 | "editor.defaultFormatter": "jock.svg" 17 | }, 18 | "git.detectSubmodulesLimit": 99 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/messaging/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Lockfiles - See https://github.com/PlasmoHQ/p1asm0 4 | pnpm-lock.yaml 5 | package-lock.json 6 | yarn.lock 7 | 8 | .turbo 9 | 10 | key.json 11 | dist/ 12 | -------------------------------------------------------------------------------- /api/messaging/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/messaging/jest.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('@jest/types').Config.InitialOptions} 3 | */ 4 | 5 | const config = { 6 | clearMocks: true, 7 | testEnvironment: "jsdom", 8 | extensionsToTreatAsEsm: [".ts"], 9 | transform: { 10 | "^.+.ts?$": [ 11 | "ts-jest", 12 | { 13 | useESM: true, 14 | isolatedModules: true 15 | } 16 | ] 17 | }, 18 | testMatch: ["**/*.test.ts"], 19 | verbose: true 20 | } 21 | export default config 22 | -------------------------------------------------------------------------------- /api/messaging/src/background.ts: -------------------------------------------------------------------------------- 1 | import type { PlasmoMessaging, PortName } from "./index" 2 | import { getExtRuntime } from "./utils" 3 | 4 | export const getPortMap = (): Map => 5 | globalThis.__plasmoInternalPortMap 6 | 7 | export const getPort = (name: PortName): chrome.runtime.Port => { 8 | const portMap = getPortMap() 9 | const port = portMap.get(name) 10 | if (!port) { 11 | throw new Error(`Port ${name} not found`) 12 | } 13 | return port 14 | } 15 | 16 | getExtRuntime().onMessage.addListener( 17 | (request: PlasmoMessaging.InternalRequest, _sender, sendResponse) => { 18 | switch (request.__PLASMO_INTERNAL_SIGNAL__) { 19 | case "__PLASMO_MESSAGING_PING__": { 20 | sendResponse(true) 21 | break 22 | } 23 | } 24 | 25 | return true 26 | } 27 | ) 28 | -------------------------------------------------------------------------------- /api/messaging/src/hook.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react" 2 | 3 | import { relayMessage, type MessageName, type PlasmoMessaging } from "./index" 4 | import { listen as messageListen } from "./message" 5 | import { listen as portListen } from "./port" 6 | import { relay } from "./relay" 7 | 8 | /** 9 | * Used in any extension context to listen and send messages to background. 10 | */ 11 | export const useMessage = ( 12 | handler: PlasmoMessaging.Handler 13 | ) => { 14 | const [data, setData] = useState() 15 | 16 | useEffect( 17 | () => 18 | messageListen(async (req, res) => { 19 | setData(req.body) 20 | await handler(req, res) 21 | }), 22 | [handler] 23 | ) 24 | 25 | return { 26 | data 27 | } 28 | } 29 | 30 | export const usePort: PlasmoMessaging.PortHook = (name) => { 31 | const portRef = useRef(undefined) 32 | const reconnectRef = useRef(0) 33 | const [data, setData] = useState() 34 | 35 | useEffect(() => { 36 | if (!name) { 37 | return null 38 | } 39 | 40 | const { port, disconnect } = portListen( 41 | name, 42 | (msg) => { 43 | setData(msg) 44 | }, 45 | () => { 46 | reconnectRef.current = reconnectRef.current + 1 47 | } 48 | ) 49 | 50 | portRef.current = port 51 | return disconnect 52 | }, [ 53 | name, 54 | reconnectRef.current // This is needed to force a new port ref 55 | ]) 56 | 57 | return { 58 | data, 59 | send: (body) => { 60 | portRef.current.postMessage({ 61 | name, 62 | body 63 | }) 64 | }, 65 | listen: (handler) => portListen(name, handler) 66 | } 67 | } 68 | 69 | /** 70 | * TODO: Perhaps add a way to detect if this hook is being used inside CS? 71 | */ 72 | export function useMessageRelay( 73 | req: PlasmoMessaging.Request 74 | ) { 75 | useEffect(() => relayMessage(req), []) 76 | } 77 | 78 | export const useRelay: PlasmoMessaging.RelayFx = (req, onMessage) => { 79 | const relayRef = useRef<() => void>(undefined) 80 | 81 | useEffect(() => { 82 | relayRef.current = relay(req, onMessage) 83 | return relayRef.current 84 | }, []) 85 | 86 | return () => relayRef.current?.() 87 | } 88 | -------------------------------------------------------------------------------- /api/messaging/src/index.ts: -------------------------------------------------------------------------------- 1 | import { relay as rawRelay, sendViaRelay as rawSendViaRelay } from "./relay" 2 | import type { MessageName, PlasmoMessaging } from "./types" 3 | import { getActiveTab, getExtRuntime, getExtTabs } from "./utils" 4 | 5 | export type { 6 | PlasmoMessaging, 7 | MessageName, 8 | PortName, 9 | PortsMetadata, 10 | MessagesMetadata, 11 | OriginContext 12 | } from "./types" 13 | 14 | /** 15 | * Send to Background Service Workers from Content Scripts or Extension pages. 16 | * `extensionId` is required to send a message from a Content Script in the main world 17 | */ 18 | // TODO: Add a framework runtime check, using a global variable 19 | export const sendToBackground: PlasmoMessaging.SendFx = async ( 20 | req 21 | ) => { 22 | return getExtRuntime().sendMessage(req.extensionId ?? null, req) 23 | } 24 | 25 | /** 26 | * Send to Content Scripts from Extension pages or Background Service Workers. 27 | * Default to active tab if no tabId is provided in the request 28 | */ 29 | export const sendToContentScript: PlasmoMessaging.SendFx = async (req) => { 30 | const tabId = 31 | typeof req.tabId === "number" ? req.tabId : (await getActiveTab())?.id 32 | 33 | if (!tabId) { 34 | throw new Error("No active tab found to send message to.") 35 | } 36 | 37 | return getExtTabs().sendMessage(tabId, req) 38 | } 39 | 40 | /** 41 | * @deprecated Renamed to `sendToContentScript` 42 | */ 43 | 44 | export const sendToActiveContentScript = sendToContentScript 45 | 46 | /** 47 | * Any request sent to this relay get send to background, then emitted back as a response 48 | */ 49 | export const relayMessage: PlasmoMessaging.MessageRelayFx = (req) => 50 | rawRelay(req, sendToBackground) 51 | 52 | /** 53 | * @deprecated Migrated to `relayMessage` 54 | */ 55 | export const relay = relayMessage 56 | 57 | export const sendToBackgroundViaRelay: PlasmoMessaging.SendFx = 58 | rawSendViaRelay 59 | 60 | /** 61 | * @deprecated Migrated to `sendToBackgroundViaRelay` 62 | */ 63 | export const sendViaRelay = sendToBackgroundViaRelay 64 | -------------------------------------------------------------------------------- /api/messaging/src/message.ts: -------------------------------------------------------------------------------- 1 | import { type PlasmoMessaging } from "./index" 2 | import { getExtRuntime } from "./utils" 3 | 4 | export const listen = ( 5 | handler: PlasmoMessaging.Handler 6 | ) => { 7 | const metaListener = async ( 8 | req: any, 9 | sender: chrome.runtime.MessageSender, 10 | sendResponse: (response?: ResponseBody) => void 11 | ) => { 12 | await handler?.( 13 | { 14 | ...req, 15 | sender 16 | }, 17 | { 18 | send: (p) => sendResponse(p) 19 | } 20 | ) 21 | } 22 | 23 | const listener = ( 24 | req: any, 25 | sender: chrome.runtime.MessageSender, 26 | sendResponse: (response?: ResponseBody) => void 27 | ) => { 28 | metaListener(req, sender, sendResponse) 29 | return true // Synchronous return to indicate this is an async listener 30 | } 31 | 32 | getExtRuntime().onMessage.addListener(listener) 33 | return () => { 34 | getExtRuntime().onMessage.removeListener(listener) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/messaging/src/port.ts: -------------------------------------------------------------------------------- 1 | import type { PortName } from "./index" 2 | import { getExtRuntime } from "./utils" 3 | 4 | const portMap = new Map() 5 | 6 | export const getPort = (name: PortName) => { 7 | const port = portMap.get(name) 8 | if (!!port) { 9 | return port 10 | } 11 | const newPort = getExtRuntime().connect({ name }) 12 | portMap.set(name, newPort) 13 | return newPort 14 | } 15 | 16 | export const removePort = (name: PortName) => { 17 | portMap.delete(name) 18 | } 19 | 20 | export const listen = ( 21 | name: PortName, 22 | handler: (msg: ResponseBody) => Promise | void, 23 | onReconnect?: () => void 24 | ) => { 25 | const port = getPort(name) 26 | 27 | function reconnectHandler() { 28 | removePort(name) 29 | onReconnect?.() 30 | } 31 | 32 | port.onMessage.addListener(handler) 33 | port.onDisconnect.addListener(reconnectHandler) 34 | 35 | return { 36 | port, 37 | disconnect: () => { 38 | port.onMessage.removeListener(handler) 39 | port.onDisconnect.removeListener(reconnectHandler) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /api/messaging/src/pub-sub.ts: -------------------------------------------------------------------------------- 1 | import { getExtRuntime } from "./utils" 2 | 3 | export type PubSubMessage = { 4 | from?: number 5 | to?: number 6 | payload: any 7 | } 8 | 9 | // Only usable from BGSW 10 | export const getHubMap = (): Map => 11 | globalThis.__plasmoInternalHubMap 12 | 13 | // Only usable by BGSW 14 | export const startHub = () => { 15 | const runtime = getExtRuntime() 16 | if (!runtime.onConnectExternal) { 17 | throw new Error( 18 | "onConnect External not available. You need externally_connectable entry possibly" 19 | ) 20 | } 21 | 22 | globalThis.__plasmoInternalHubMap = new Map() 23 | const hub = getHubMap() 24 | 25 | runtime.onConnectExternal.addListener((port) => { 26 | const tabId = port.sender.tab.id 27 | if (!hub.has(tabId)) { 28 | hub.set(tabId, port) 29 | port.onMessage.addListener((message) => { 30 | broadcast({ from: tabId, payload: message }) 31 | }) 32 | port.onDisconnect.addListener(() => { 33 | //TODO - Should we log? 34 | hub.delete(tabId) 35 | }) 36 | } 37 | }) 38 | } 39 | 40 | // Only usable by BGSW 41 | export const broadcast = (pubSubMessage: PubSubMessage) => { 42 | const hub = getHubMap() 43 | hub.forEach((port, tabId) => { 44 | const skipBroadcast = tabId === pubSubMessage.from 45 | if (skipBroadcast) { 46 | return 47 | } 48 | port.postMessage({ ...pubSubMessage, to: tabId }) 49 | }) 50 | } 51 | 52 | export const connectToHub = (extensionId: string) => { 53 | const runtime = getExtRuntime() 54 | if (!runtime.connect) { 55 | throw new Error( 56 | "runtime.connect not available. You need to use startHub in BGSW" 57 | ) 58 | } 59 | const port = runtime.connect(extensionId) 60 | return port 61 | } 62 | -------------------------------------------------------------------------------- /api/messaging/src/relay.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid" 2 | 3 | import type { PlasmoMessaging } from "./index" 4 | import { isSameOrigin } from "./utils" 5 | 6 | /** 7 | * Raw relay abstracting window.postMessage 8 | */ 9 | export const relay: PlasmoMessaging.RelayFx = ( 10 | req, 11 | onMessage, 12 | messagePort = globalThis.window 13 | ) => { 14 | const relayHandler = async ( 15 | event: MessageEvent 16 | ) => { 17 | if (isSameOrigin(event, req) && !event.data.relayed) { 18 | const relayPayload = { 19 | name: req.name, 20 | relayId: req.relayId, 21 | body: event.data.body 22 | } 23 | 24 | const backgroundResponse = await onMessage?.(relayPayload) 25 | 26 | messagePort.postMessage( 27 | { 28 | name: req.name, 29 | relayId: req.relayId, 30 | instanceId: event.data.instanceId, 31 | body: backgroundResponse, 32 | relayed: true 33 | }, 34 | { 35 | targetOrigin: req.targetOrigin || "/" 36 | } 37 | ) 38 | } 39 | } 40 | 41 | messagePort.addEventListener("message", relayHandler) 42 | return () => messagePort.removeEventListener("message", relayHandler) 43 | } 44 | 45 | export const sendViaRelay: PlasmoMessaging.SendFx = ( 46 | req, 47 | messagePort = globalThis.window 48 | ) => 49 | new Promise((resolve, _reject) => { 50 | const instanceId = nanoid() 51 | const abortController = new AbortController() 52 | messagePort.addEventListener( 53 | "message", 54 | (event: MessageEvent) => { 55 | if ( 56 | isSameOrigin(event, req) && 57 | event.data.relayed && 58 | event.data.instanceId === instanceId 59 | ) { 60 | resolve(event.data.body) 61 | abortController.abort() 62 | } 63 | }, 64 | { 65 | signal: abortController.signal 66 | } 67 | ) 68 | 69 | messagePort.postMessage( 70 | { 71 | ...req, 72 | instanceId 73 | } as PlasmoMessaging.RelayMessage, 74 | { 75 | targetOrigin: req.targetOrigin || "/" 76 | } 77 | ) 78 | }) 79 | -------------------------------------------------------------------------------- /api/messaging/src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { PlasmoMessaging } from "./index" 2 | 3 | const extTabs = (globalThis.browser?.tabs || 4 | globalThis.chrome?.tabs) as typeof chrome.tabs 5 | 6 | export const getExtRuntime = () => { 7 | // TODO: Move this to a broader utils package later on 8 | const extRuntime = (globalThis.browser?.runtime || 9 | globalThis.chrome?.runtime) as typeof chrome.runtime 10 | 11 | if (!extRuntime) { 12 | throw new Error("Extension runtime is not available") 13 | } 14 | return extRuntime 15 | } 16 | 17 | export const getExtTabs = () => { 18 | if (!extTabs) { 19 | throw new Error("Extension tabs API is not available") 20 | } 21 | return extTabs 22 | } 23 | 24 | export const getActiveTab = async () => { 25 | const extTabs = getExtTabs() 26 | const [tab] = await extTabs.query({ 27 | active: true, 28 | currentWindow: true 29 | }) 30 | return tab as chrome.tabs.Tab | undefined 31 | } 32 | 33 | export const isSameOrigin = ( 34 | event: MessageEvent, 35 | req: any 36 | ): req is PlasmoMessaging.Request => 37 | !req.__internal && 38 | event.source === globalThis.window && 39 | event.data.name === req.name && 40 | (req.relayId === undefined || event.data.relayId === req.relayId) 41 | 42 | export const getRuntimeContext = () => { 43 | // If chrome API available but they cannot access 44 | // OR we can mark them directly (?), by injecting a tag at runtime itself 45 | } 46 | -------------------------------------------------------------------------------- /api/messaging/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "resolveJsonModule": true, 5 | "sourceMap": true, 6 | "strict": false, 7 | "declaration": true, 8 | "allowSyntheticDefaultImports": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noImplicitReturns": true, 11 | "esModuleInterop": true, 12 | "noImplicitAny": false, 13 | "moduleResolution": "node", 14 | 15 | "module": "ESNext", 16 | "target": "ESNext", 17 | "allowJs": true, 18 | "verbatimModuleSyntax": true 19 | }, 20 | "include": ["src/**/*.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /api/persistent/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Lockfiles - See https://github.com/PlasmoHQ/p1asm0 4 | pnpm-lock.yaml 5 | package-lock.json 6 | yarn.lock 7 | 8 | .turbo 9 | 10 | key.json 11 | dist/ 12 | -------------------------------------------------------------------------------- /api/persistent/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/persistent/README.md: -------------------------------------------------------------------------------- 1 | # Plasmo Persistent runtime 2 | 3 | This library contains a couple of hacks to keep the BGSW alive for MV3 transitioning. 4 | 5 | Usage in a background service worker: 6 | 7 | ```ts 8 | import { keepAlive } from "@plasmohq/persistent/background" 9 | 10 | keepAlive() 11 | ``` 12 | -------------------------------------------------------------------------------- /api/persistent/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/persistent", 3 | "version": "0.0.6", 4 | "description": "A couple of hacks to keep the BGSW alive in a library", 5 | "type": "module", 6 | "module": "./src/index.ts", 7 | "types": "./src/index.ts", 8 | "typesVersions": { 9 | "*": { 10 | "background": [ 11 | "./src/background.ts" 12 | ] 13 | } 14 | }, 15 | "publishConfig": { 16 | "module": "./dist/index.js", 17 | "types": "./dist/index.d.ts", 18 | "typesVersions": { 19 | "*": { 20 | "background": [ 21 | "./dist/background.d.ts" 22 | ] 23 | } 24 | } 25 | }, 26 | "exports": { 27 | "./background": { 28 | "types": "./dist/background.d.ts", 29 | "import": "./dist/background.js", 30 | "require": "./dist/background.cjs" 31 | }, 32 | ".": { 33 | "types": "./dist/index.d.ts", 34 | "import": "./dist/index.js", 35 | "require": "./dist/index.cjs" 36 | } 37 | }, 38 | "files": [ 39 | "dist" 40 | ], 41 | "scripts": { 42 | "dev": "run-p dev:*", 43 | "dev:compile": "tsup --watch", 44 | "dev:test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch", 45 | "build": "tsup", 46 | "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest", 47 | "prepublishOnly": "pnpm build" 48 | }, 49 | "author": "Plasmo Corp. ", 50 | "contributors": [ 51 | "@louisgv" 52 | ], 53 | "repository": { 54 | "type": "git", 55 | "url": "https://github.com/PlasmoHQ/plasmo.git", 56 | "directory": "api/persistent" 57 | }, 58 | "license": "MIT", 59 | "keywords": [ 60 | "browser-extension", 61 | "chrome-extension" 62 | ], 63 | "devDependencies": { 64 | "@jest/globals": "29.7.0", 65 | "@jest/types": "29.6.3", 66 | "@plasmo/config": "workspace:*", 67 | "@types/chrome": "0.0.312", 68 | "@types/node": "22.13.13", 69 | "cross-env": "7.0.3", 70 | "jest": "29.7.0", 71 | "jest-environment-jsdom": "29.7.0", 72 | "ts-jest": "29.3.0", 73 | "tsup": "8.4.0", 74 | "typescript": "5.8.2" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /api/persistent/src/background.ts: -------------------------------------------------------------------------------- 1 | export const keepAlive = () => { 2 | const extRuntime = (globalThis.browser?.runtime || 3 | globalThis.chrome?.runtime) as typeof chrome.runtime 4 | const _keepAlive = () => setInterval(extRuntime.getPlatformInfo, 24_000) // 24 seconds 5 | extRuntime.onStartup.addListener(_keepAlive) 6 | _keepAlive() 7 | } 8 | -------------------------------------------------------------------------------- /api/persistent/src/index.ts: -------------------------------------------------------------------------------- 1 | export const life = 42 2 | -------------------------------------------------------------------------------- /api/persistent/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/utils", 3 | "include": ["src/**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /api/persistent/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup" 2 | 3 | export default defineConfig((opt) => { 4 | const isProd = !opt.watch 5 | return { 6 | entry: ["src/index.ts", "src/background.ts"], 7 | 8 | format: ["esm", "cjs"], 9 | 10 | target: "esnext", 11 | platform: "node", 12 | splitting: false, 13 | bundle: true, 14 | dts: true, 15 | 16 | watch: opt.watch, 17 | sourcemap: !isProd, 18 | minify: isProd, 19 | clean: isProd 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /api/selector/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Lockfiles - See https://github.com/PlasmoHQ/p1asm0 4 | pnpm-lock.yaml 5 | package-lock.json 6 | yarn.lock 7 | 8 | .turbo 9 | 10 | key.json 11 | dist/ 12 | -------------------------------------------------------------------------------- /api/selector/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/selector/src/background.ts: -------------------------------------------------------------------------------- 1 | import type { SelectorMessage } from "./types" 2 | 3 | // Simple cache, it won't persist, but it will do for now 4 | const softCache = new Set() 5 | 6 | async function selectorMessageHandler( 7 | message: SelectorMessage, 8 | monitorId: string, 9 | sample: number 10 | ) { 11 | switch (message.name) { 12 | case "plasmo:selector:invalid": { 13 | if (!monitorId) { 14 | return 15 | } 16 | 17 | const body = JSON.stringify({ 18 | monitorId, 19 | payload: message.payload 20 | }) 21 | 22 | if (softCache.has(body) || Math.random() > sample) { 23 | return 24 | } 25 | 26 | try { 27 | softCache.add(body) 28 | await fetch( 29 | `${process.env.ITERO_MONITOR_API_BASE_URI}/api/selector/invalid`, 30 | { 31 | method: "POST", 32 | headers: { 33 | "Content-Type": "application/json" 34 | }, 35 | body 36 | } 37 | ) 38 | } catch {} 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * @param monitorId id of the monitor to send invalid selectors to 45 | * @param sample percentage of invalid selectors to send to the monitor, default 47% 46 | */ 47 | export const init = ({ monitorId = "", sample = 0.47 }) => { 48 | chrome.runtime.onMessage.addListener((message: SelectorMessage) => { 49 | selectorMessageHandler(message, monitorId, sample) 50 | return true 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /api/selector/src/hook.ts: -------------------------------------------------------------------------------- 1 | export const useSelector = () => {} 2 | -------------------------------------------------------------------------------- /api/selector/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { SelectorMessage } from "./types" 2 | 3 | async function sendInvalidSelectors(selectors: string[]) { 4 | try { 5 | return await chrome?.runtime?.sendMessage({ 6 | name: "plasmo:selector:invalid", 7 | payload: { 8 | selectors, 9 | url: window.location.href 10 | } 11 | } as SelectorMessage) 12 | } catch {} 13 | } 14 | 15 | export const querySelectors = (selectors: string[]) => { 16 | const result: Element[] = [] 17 | const invalidSelectors: string[] = [] 18 | for (const selector of selectors) { 19 | const element = document.querySelector(selector) 20 | if (!element) { 21 | invalidSelectors.push(selector) 22 | } else { 23 | result.push(element) 24 | } 25 | } 26 | 27 | if (invalidSelectors.length > 0) { 28 | sendInvalidSelectors(invalidSelectors) 29 | } 30 | 31 | return result 32 | } 33 | 34 | export const querySelector = (selector: string) => { 35 | const element = document.querySelector(selector) 36 | if (!element) { 37 | sendInvalidSelectors([selector]) 38 | } 39 | 40 | return element 41 | } 42 | 43 | export const querySelectorAll = (selector: string) => { 44 | const elements = document.querySelectorAll(selector) 45 | 46 | if (elements.length === 0) { 47 | sendInvalidSelectors([selector]) 48 | } 49 | 50 | return elements 51 | } 52 | -------------------------------------------------------------------------------- /api/selector/src/monitor.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | document.querySelector = new Proxy(document.querySelector, { 4 | apply: (target, thisArg, args) => {} 5 | }) 6 | -------------------------------------------------------------------------------- /api/selector/src/types.ts: -------------------------------------------------------------------------------- 1 | export type SelectorMessage = { 2 | name: "plasmo:selector:invalid" 3 | payload: { 4 | selectors: string[] 5 | url: string 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /api/selector/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "resolveJsonModule": true, 5 | "sourceMap": true, 6 | "strict": false, 7 | "declaration": true, 8 | "allowSyntheticDefaultImports": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noImplicitReturns": true, 11 | "esModuleInterop": true, 12 | "noImplicitAny": false, 13 | "moduleResolution": "node", 14 | 15 | "module": "ESNext", 16 | "target": "ESNext", 17 | "allowJs": true, 18 | "verbatimModuleSyntax": true, 19 | "lib": ["DOM"] 20 | }, 21 | "include": ["src/**/*.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /api/selector/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup" 2 | 3 | export default defineConfig((opt) => { 4 | const isProd = !opt.watch 5 | return { 6 | entry: [ 7 | "src/index.ts", 8 | "src/monitor.ts", 9 | "src/background.ts", 10 | "src/hook.ts" 11 | ], 12 | 13 | format: ["esm", "cjs"], 14 | 15 | target: "esnext", 16 | platform: "node", 17 | splitting: false, 18 | bundle: true, 19 | dts: true, 20 | 21 | env: { 22 | ITERO_MONITOR_API_BASE_URI: isProd 23 | ? "https://itero.plasmo.com" 24 | : "http://localhost:3000" 25 | }, 26 | 27 | watch: opt.watch, 28 | sourcemap: !isProd, 29 | minify: isProd, 30 | clean: isProd 31 | } 32 | }) 33 | -------------------------------------------------------------------------------- /cli/create-plasmo/bin/index.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import "../dist/index.js" 4 | -------------------------------------------------------------------------------- /cli/create-plasmo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-plasmo", 3 | "version": "0.90.5", 4 | "description": "Create Plasmo Framework Browser Extension", 5 | "main": "dist/index.js", 6 | "bin": "bin/index.mjs", 7 | "type": "module", 8 | "files": [ 9 | "bin", 10 | "dist" 11 | ], 12 | "tsup": { 13 | "format": "esm", 14 | "target": "esnext", 15 | "platform": "node", 16 | "splitting": false, 17 | "bundle": true, 18 | "minify": true, 19 | "clean": true, 20 | "banner": { 21 | "js": "import { createRequire } from 'module';const require = createRequire(import.meta.url);" 22 | } 23 | }, 24 | "scripts": { 25 | "build": "tsup src/index.ts", 26 | "prepublishOnly": "run-s build" 27 | }, 28 | "author": "Plasmo Corp. ", 29 | "homepage": "https://docs.plasmo.com/", 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/PlasmoHQ/plasmo.git", 33 | "directory": "cli/create-plasmo" 34 | }, 35 | "license": "MIT", 36 | "keywords": [ 37 | "plasmo", 38 | "browser-extensions", 39 | "framework" 40 | ], 41 | "dependencies": { 42 | "@plasmohq/init": "workspace:*" 43 | }, 44 | "devDependencies": { 45 | "@plasmo/config": "workspace:*", 46 | "@plasmo/constants": "workspace:*", 47 | "@plasmo/utils": "workspace:*", 48 | "plasmo": "workspace:*", 49 | "typescript": "5.8.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cli/create-plasmo/src/commands.ts: -------------------------------------------------------------------------------- 1 | export const validCommandList = [] 2 | -------------------------------------------------------------------------------- /cli/create-plasmo/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { argv, exit } from "process" 3 | import { version } from "plasmo/package.json" 4 | import init from "plasmo/src/commands/init" 5 | 6 | import { ErrorMessage } from "@plasmo/constants/error" 7 | import { aLog, eLog } from "@plasmo/utils/logging" 8 | import { exitCountDown } from "@plasmo/utils/wait" 9 | 10 | process.env.APP_VERSION = version 11 | 12 | async function main() { 13 | try { 14 | // In case someone pasted an essay into the cli 15 | if (argv.length > 10) { 16 | throw new Error(ErrorMessage.TooManyArg) 17 | } 18 | 19 | argv.splice(2, 0, "init") 20 | 21 | await init() 22 | } catch (e) { 23 | eLog((e as Error).message || ErrorMessage.Unknown) 24 | aLog(e.stack) 25 | await exitCountDown(3) 26 | exit(1) 27 | } 28 | } 29 | 30 | main() 31 | 32 | process.on("SIGINT", () => exit(0)) 33 | process.on("SIGTERM", () => exit(0)) 34 | -------------------------------------------------------------------------------- /cli/create-plasmo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/cli.json", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules", "templates"], 5 | "compilerOptions": { 6 | "outDir": "dist", 7 | "baseUrl": ".", 8 | "paths": { 9 | "~features/*": ["../plasmo/src/features/*"], 10 | "~commands": ["./src/commands"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cli/plasmo/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require("@plasmo/config/eslint-preset") 2 | -------------------------------------------------------------------------------- /cli/plasmo/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /cli/plasmo/bin/index.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import "../dist/index.js" 4 | -------------------------------------------------------------------------------- /cli/plasmo/index.mjs: -------------------------------------------------------------------------------- 1 | import { argv, exit } from "process" 2 | import { build, context } from "esbuild" 3 | import fse from "fs-extra" 4 | 5 | const watch = argv.includes("-w") 6 | 7 | /** @type import('esbuild').BuildOptions */ 8 | const commonConfig = { 9 | sourcemap: watch ? "inline" : false, 10 | minify: !watch, 11 | logLevel: watch ? "info" : "warning", 12 | 13 | bundle: true 14 | } 15 | 16 | async function main() { 17 | const config = await fse.readJson("package.json") 18 | const define = { 19 | "process.env.APP_VERSION": `"${config.version}"` 20 | } 21 | 22 | /** @type import('esbuild').BuildOptions */ 23 | const opts = { 24 | ...commonConfig, 25 | entryPoints: ["src/index.ts"], 26 | external: Object.keys(config.dependencies), 27 | platform: "node", 28 | format: "esm", 29 | define, 30 | banner: { 31 | js: "import { createRequire } from 'module';const require = createRequire(import.meta.url);" 32 | }, 33 | outfile: "dist/index.js" 34 | } 35 | 36 | if (watch) { 37 | const ctx = await context(opts) 38 | await ctx.watch() 39 | } else { 40 | await build(opts) 41 | } 42 | } 43 | 44 | main() 45 | 46 | process.on("SIGINT", () => exit(0)) 47 | process.on("SIGTERM", () => exit(0)) 48 | -------------------------------------------------------------------------------- /cli/plasmo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plasmo", 3 | "version": "0.90.5", 4 | "description": "The Plasmo Framework CLI", 5 | "publishConfig": { 6 | "types": "dist/type.d.ts" 7 | }, 8 | "types": "src/type.ts", 9 | "main": "dist/index.js", 10 | "bin": "bin/index.mjs", 11 | "type": "module", 12 | "files": [ 13 | "bin/index.mjs", 14 | "dist/index.js", 15 | "dist/type.d.ts", 16 | "templates" 17 | ], 18 | "scripts": { 19 | "dev": "node index.mjs -w", 20 | "build": "node index.mjs", 21 | "type": "tsup src/type.ts --format esm --dts-only --dts-resolve", 22 | "prepublishOnly": "run-p type build", 23 | "lint": "run-p lint:*", 24 | "lint:type": "tsc --noemit", 25 | "lint:code": "eslint src/**/*.ts" 26 | }, 27 | "author": "Plasmo Corp. ", 28 | "homepage": "https://docs.plasmo.com/", 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/PlasmoHQ/plasmo.git", 32 | "directory": "cli/plasmo" 33 | }, 34 | "license": "MIT", 35 | "keywords": [ 36 | "plasmo", 37 | "browser-extensions", 38 | "framework" 39 | ], 40 | "dependencies": { 41 | "@expo/spawn-async": "1.7.2", 42 | "@parcel/core": "2.9.3", 43 | "@parcel/fs": "2.9.3", 44 | "@parcel/package-manager": "2.9.3", 45 | "@parcel/watcher": "2.5.1", 46 | "@plasmohq/init": "workspace:*", 47 | "@plasmohq/parcel-config": "workspace:*", 48 | "@plasmohq/parcel-core": "workspace:*", 49 | "buffer": "6.0.3", 50 | "chalk": "5.4.1", 51 | "change-case": "5.4.4", 52 | "dotenv": "16.4.7", 53 | "dotenv-expand": "12.0.1", 54 | "events": "3.3.0", 55 | "fast-glob": "3.3.3", 56 | "fflate": "0.8.2", 57 | "get-port": "7.1.0", 58 | "got": "14.4.6", 59 | "ignore": "7.0.3", 60 | "inquirer": "12.5.0", 61 | "is-path-inside": "4.0.0", 62 | "json5": "2.2.3", 63 | "mnemonic-id": "3.2.7", 64 | "node-object-hash": "3.1.1", 65 | "package-json": "10.0.1", 66 | "process": "0.11.10", 67 | "semver": "7.7.1", 68 | "sharp": "0.33.5", 69 | "tempy": "3.1.0", 70 | "typescript": "5.8.2" 71 | }, 72 | "devDependencies": { 73 | "@plasmo/config": "workspace:*", 74 | "@plasmo/constants": "workspace:*", 75 | "@plasmo/framework-shared": "workspace:*", 76 | "@plasmo/utils": "workspace:*", 77 | "vue": "3.5.13" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /cli/plasmo/src/commands/build.ts: -------------------------------------------------------------------------------- 1 | import { getNonFlagArgvs } from "@plasmo/utils/argv" 2 | import { hasFlag } from "@plasmo/utils/flags" 3 | import { iLog, sLog } from "@plasmo/utils/logging" 4 | 5 | import { getBundleConfig } from "~features/extension-devtools/get-bundle-config" 6 | import { nextNewTab } from "~features/extra/next-new-tab" 7 | import { checkNewVersion } from "~features/framework-update/version-tracker" 8 | import { createParcelBuilder } from "~features/helpers/create-parcel-bundler" 9 | import { printHeader } from "~features/helpers/print" 10 | import { createManifest } from "~features/manifest-factory/create-manifest" 11 | import { zipBundle } from "~features/manifest-factory/zip" 12 | 13 | async function build() { 14 | printHeader() 15 | checkNewVersion() 16 | 17 | process.env.NODE_ENV = "production" 18 | 19 | const [internalCmd] = getNonFlagArgvs("build") 20 | 21 | if (internalCmd === "next-new-tab") { 22 | await nextNewTab() 23 | return 24 | } 25 | 26 | iLog("Prepare to bundle the extension...") 27 | 28 | const bundleConfig = getBundleConfig() 29 | 30 | iLog("Building for target:", bundleConfig.target) 31 | 32 | const plasmoManifest = await createManifest(bundleConfig) 33 | 34 | const bundler = await createParcelBuilder(plasmoManifest, { 35 | mode: "production", 36 | shouldDisableCache: true, 37 | shouldContentHash: false, 38 | defaultTargetOptions: { 39 | shouldOptimize: true, 40 | shouldScopeHoist: hasFlag("--hoist") 41 | } 42 | }) 43 | 44 | const result = await bundler.run() 45 | sLog(`Finished in ${result.buildTime}ms!`) 46 | 47 | await plasmoManifest.postBuild() 48 | 49 | if (hasFlag("--zip")) { 50 | await zipBundle(plasmoManifest.commonPath) 51 | } 52 | } 53 | 54 | export default build 55 | -------------------------------------------------------------------------------- /cli/plasmo/src/commands/help.ts: -------------------------------------------------------------------------------- 1 | import { printHeader, printHelp } from "~features/helpers/print" 2 | 3 | async function help() { 4 | printHeader() 5 | printHelp() 6 | } 7 | 8 | export default help 9 | -------------------------------------------------------------------------------- /cli/plasmo/src/commands/index.ts: -------------------------------------------------------------------------------- 1 | export const runMap = { 2 | help: () => import("./help"), 3 | 4 | //#ifdef !IS_BINARY 5 | start: () => import("./start"), 6 | init: () => import("./init"), 7 | dev: () => import("./dev"), 8 | build: () => import("./build"), 9 | package: () => import("./package"), 10 | //#endif 11 | 12 | version: () => import("./version"), 13 | ["-v"]: () => import("./version"), 14 | ["--version"]: () => import("./version") 15 | } 16 | 17 | export type ValidCommand = keyof typeof runMap 18 | 19 | export const validCommandList = Object.keys(runMap) as ValidCommand[] 20 | 21 | export const validCommandSet = new Set(validCommandList) 22 | -------------------------------------------------------------------------------- /cli/plasmo/src/commands/init.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path" 2 | import { cwd } from "process" 3 | import { kebabCase } from "change-case" 4 | 5 | import { hasFlag } from "@plasmo/utils/flags" 6 | import { ensureWritableAndEmpty } from "@plasmo/utils/fs" 7 | import { vLog } from "@plasmo/utils/logging" 8 | 9 | import { getCommonPath } from "~features/extension-devtools/common-path" 10 | import { getPackageManager } from "~features/helpers/package-manager" 11 | import { printHeader } from "~features/helpers/print" 12 | import { ProjectCreator } from "~features/project-creator" 13 | import { getRawName } from "~features/project-creator/get-raw-name" 14 | import { gitInit } from "~features/project-creator/git-init" 15 | import { installDependencies } from "~features/project-creator/install-dependencies" 16 | import { printReady } from "~features/project-creator/print-ready" 17 | 18 | async function init() { 19 | printHeader() 20 | 21 | const isExample = hasFlag("--exp") 22 | const rawName = await getRawName() 23 | 24 | const currentDirectory = cwd() 25 | 26 | // For resolving project directory 27 | const projectDirectory = resolve( 28 | currentDirectory, 29 | kebabCase(rawName) || rawName 30 | ) 31 | 32 | vLog("Project directory:", projectDirectory) 33 | 34 | const commonPath = getCommonPath(projectDirectory) 35 | 36 | vLog("Package name:", commonPath.packageName) 37 | 38 | if (isExample && !commonPath.packageName.startsWith("with-")) { 39 | throw new Error("Example extensions must have the `with-` prefix") 40 | } 41 | 42 | await ensureWritableAndEmpty(projectDirectory) 43 | 44 | const packageManager = await getPackageManager() 45 | vLog( 46 | `Using package manager: ${packageManager.name} ${packageManager?.version}` 47 | ) 48 | 49 | const creator = new ProjectCreator(commonPath, packageManager, isExample) 50 | await creator.create() 51 | 52 | await installDependencies(projectDirectory, packageManager) 53 | 54 | await gitInit(commonPath, projectDirectory) 55 | 56 | await printReady( 57 | projectDirectory, 58 | currentDirectory, 59 | commonPath, 60 | packageManager 61 | ) 62 | } 63 | 64 | export default init 65 | -------------------------------------------------------------------------------- /cli/plasmo/src/commands/package.ts: -------------------------------------------------------------------------------- 1 | import { hasFlag } from "@plasmo/utils/flags" 2 | import { iLog } from "@plasmo/utils/logging" 3 | 4 | import { getBundleConfig } from "~features/extension-devtools/get-bundle-config" 5 | import { checkNewVersion } from "~features/framework-update/version-tracker" 6 | import { printHeader } from "~features/helpers/print" 7 | import { createManifest } from "~features/manifest-factory/create-manifest" 8 | import { zipBundle } from "~features/manifest-factory/zip" 9 | 10 | async function packageCmd() { 11 | printHeader() 12 | checkNewVersion() 13 | 14 | process.env.NODE_ENV = "production" 15 | 16 | iLog("Prepare to package the extension bundle...") 17 | 18 | const bundleConfig = getBundleConfig() 19 | 20 | const plasmoManifest = await createManifest(bundleConfig) 21 | 22 | await zipBundle(plasmoManifest.commonPath, hasFlag("--with-source-maps")) 23 | } 24 | 25 | export default packageCmd 26 | -------------------------------------------------------------------------------- /cli/plasmo/src/commands/start.ts: -------------------------------------------------------------------------------- 1 | import { iLog } from "@plasmo/utils/logging" 2 | 3 | async function start() { 4 | iLog("Start the extension development...") 5 | } 6 | 7 | export default start 8 | -------------------------------------------------------------------------------- /cli/plasmo/src/commands/version.ts: -------------------------------------------------------------------------------- 1 | async function version() { 2 | console.log(process.env.APP_VERSION) 3 | } 4 | 5 | export default version 6 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/background-service-worker/bgsw-entry.ts: -------------------------------------------------------------------------------- 1 | import { relative, resolve } from "path" 2 | import { ensureDir, outputFile } from "fs-extra" 3 | 4 | import { vLog } from "@plasmo/utils/logging" 5 | import { toPosix } from "@plasmo/utils/path" 6 | 7 | import { type PlasmoManifest } from "~features/manifest-factory/base" 8 | 9 | export const createBgswEntry = async ( 10 | { indexFilePath = "", withMessaging = false, withMainWorldScript = false }, 11 | plasmoManifest: PlasmoManifest 12 | ) => { 13 | vLog("Creating BGSW entry") 14 | 15 | const bgswStaticDirectory = resolve( 16 | plasmoManifest.commonPath.staticDirectory, 17 | "background" 18 | ) 19 | 20 | const bgswEntryFilePath = resolve(bgswStaticDirectory, "index.ts") 21 | const indexImportPath = relative(bgswStaticDirectory, indexFilePath) 22 | 23 | const bgswCode = [ 24 | withMessaging && `import "./messaging"`, 25 | indexFilePath && `import "${toPosix(indexImportPath).slice(0, -3)}"`, 26 | withMainWorldScript && `import "./main-world-scripts"` 27 | ] 28 | .filter(Boolean) 29 | .join("\n") 30 | 31 | await ensureDir(bgswStaticDirectory) 32 | await outputFile(bgswEntryFilePath, bgswCode) 33 | } 34 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/background-service-worker/bgsw-messaging-declaration.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path" 2 | import { outputFile } from "fs-extra" 3 | 4 | import type { CommonPath } from "~features/extension-devtools/common-path" 5 | 6 | export const MESSAGING_DECLARATION = `messaging` as const 7 | 8 | const MESSAGING_DECLARATION_FILENAME = `${MESSAGING_DECLARATION}.d.ts` 9 | 10 | export const outputMessagingDeclaration = ( 11 | commonPath: CommonPath, 12 | declarationCode: string 13 | ) => 14 | outputFile( 15 | resolve(commonPath.dotPlasmoDirectory, MESSAGING_DECLARATION_FILENAME), 16 | declarationCode 17 | ) 18 | 19 | export const createDeclarationCode = (messages: string[], ports: string[]) => ` 20 | import "@plasmohq/messaging" 21 | 22 | interface MmMetadata { 23 | \t${messages.join("\n\t")} 24 | } 25 | 26 | interface MpMetadata { 27 | \t${ports.join("\n\t")} 28 | } 29 | 30 | declare module "@plasmohq/messaging" { 31 | interface MessagesMetadata extends MmMetadata {} 32 | interface PortsMetadata extends MpMetadata {} 33 | } 34 | ` 35 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/background-service-worker/update-bgsw-entry.ts: -------------------------------------------------------------------------------- 1 | import { find } from "@plasmo/utils/array" 2 | import { isAccessible } from "@plasmo/utils/fs" 3 | 4 | import { createBgswEntry } from "~features/background-service-worker/bgsw-entry" 5 | import { createBgswMainWorldInjector } from "~features/background-service-worker/bgsw-main-world-script" 6 | import { createBgswMessaging } from "~features/background-service-worker/bgsw-messaging" 7 | import { type PlasmoManifest } from "~features/manifest-factory/base" 8 | 9 | export const updateBgswEntry = async (plasmoManifest: PlasmoManifest) => { 10 | const [bgswIndexFilePath, withMessaging, withMainWorldScript] = 11 | await Promise.all([ 12 | find(plasmoManifest.projectPath.backgroundIndexList, isAccessible), 13 | createBgswMessaging(plasmoManifest), 14 | createBgswMainWorldInjector(plasmoManifest) 15 | ] as const) 16 | 17 | const hasBgsw = 18 | Boolean(bgswIndexFilePath) || withMessaging || withMainWorldScript 19 | 20 | if (hasBgsw) { 21 | await createBgswEntry( 22 | { 23 | indexFilePath: bgswIndexFilePath, 24 | withMessaging, 25 | withMainWorldScript 26 | }, 27 | plasmoManifest 28 | ) 29 | } 30 | 31 | return plasmoManifest.toggleBackground(hasBgsw) 32 | } 33 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/env/env-declaration.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path" 2 | import { outputFile } from "fs-extra" 3 | 4 | import type { PlasmoManifest } from "~features/manifest-factory/base" 5 | 6 | export const PROCESS_ENV_DECLARATION = `process.env` as const 7 | const PROCESS_ENV_DECLARATION_FILENAME = `${PROCESS_ENV_DECLARATION}.d.ts` 8 | 9 | const createDeclarationCode = (envKeys: string[]) => ` 10 | declare namespace NodeJS { 11 | interface ProcessEnv { 12 | ${envKeys.map((e) => `\t\t${e}?: string`).join("\n")} 13 | } 14 | } 15 | ` 16 | 17 | export async function outputEnvDeclaration({ 18 | commonPath, 19 | publicEnv 20 | }: PlasmoManifest) { 21 | const envKeys = Object.keys(publicEnv.data) 22 | 23 | if (envKeys.length === 0) { 24 | return 25 | } 26 | 27 | await outputFile( 28 | resolve(commonPath.dotPlasmoDirectory, PROCESS_ENV_DECLARATION_FILENAME), 29 | createDeclarationCode(envKeys) 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/extension-devtools/common-path.ts: -------------------------------------------------------------------------------- 1 | import { existsSync } from "fs" 2 | import { basename, resolve } from "path" 3 | import { cwd } from "process" 4 | 5 | import { getFlagMap } from "~features/helpers/flag" 6 | 7 | export const getCommonPath = (projectDirectory = cwd()) => { 8 | const flagMap = getFlagMap() 9 | 10 | process.env.PLASMO_PROJECT_DIR = projectDirectory 11 | 12 | const packageName = basename(projectDirectory) 13 | 14 | process.env.PLASMO_SRC_PATH = flagMap.srcPath 15 | 16 | const srcDirectory = resolve(projectDirectory, flagMap.srcPath) 17 | 18 | process.env.PLASMO_SRC_DIR = existsSync(srcDirectory) 19 | ? srcDirectory 20 | : projectDirectory 21 | 22 | process.env.PLASMO_BUILD_PATH = flagMap.buildPath 23 | 24 | const buildDirectory = resolve(projectDirectory, flagMap.buildPath) 25 | 26 | process.env.PLASMO_BUILD_DIR = buildDirectory 27 | 28 | const distDirectoryName = `${flagMap.target}-${flagMap.tag}` 29 | 30 | const distDirectory = resolve(buildDirectory, distDirectoryName) 31 | 32 | const dotPlasmoDirectory = resolve(projectDirectory, ".plasmo") 33 | 34 | const cacheDirectory = resolve(dotPlasmoDirectory, "cache") 35 | 36 | return { 37 | packageName, 38 | projectDirectory, 39 | 40 | buildDirectory, 41 | distDirectory, 42 | distDirectoryName, 43 | 44 | sourceDirectory: process.env.PLASMO_SRC_DIR, 45 | packageFilePath: resolve(projectDirectory, "package.json"), 46 | gitIgnorePath: resolve(projectDirectory, ".gitignore"), 47 | assetsDirectory: resolve(projectDirectory, "assets"), 48 | parcelConfig: resolve(projectDirectory, ".parcelrc"), 49 | 50 | dotPlasmoDirectory, 51 | cacheDirectory, 52 | 53 | plasmoVersionFilePath: resolve(cacheDirectory, "plasmo.version.json"), 54 | 55 | staticDirectory: resolve(dotPlasmoDirectory, "static"), 56 | genAssetsDirectory: resolve(dotPlasmoDirectory, "gen-assets"), 57 | entryManifestPath: resolve( 58 | dotPlasmoDirectory, 59 | `${flagMap.target}.plasmo.manifest.json` 60 | ) 61 | } 62 | } 63 | 64 | export type CommonPath = ReturnType 65 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/extension-devtools/content-script-config.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "fs/promises" 2 | import typescript, { type Node, type VariableDeclaration } from "typescript" 3 | 4 | import type { ManifestContentScript } from "@plasmo/constants" 5 | import { eLog, vLog } from "@plasmo/utils/logging" 6 | 7 | import { parseAst } from "./parse-ast" 8 | 9 | const { 10 | ScriptTarget, 11 | SyntaxKind, 12 | createSourceFile, 13 | isObjectLiteralExpression, 14 | isVariableStatement 15 | } = typescript 16 | 17 | export const extractContentScriptConfig = async (path: string) => { 18 | try { 19 | const sourceContent = await readFile(path, "utf8") 20 | if (sourceContent.length === 0) { 21 | return { 22 | isEmpty: true 23 | } 24 | } 25 | 26 | const sourceFile = createSourceFile( 27 | path, 28 | sourceContent, 29 | ScriptTarget.Latest, 30 | true 31 | ) 32 | 33 | const variableDeclarationMap = sourceFile.statements 34 | .filter(isVariableStatement) 35 | .reduce( 36 | (output, node) => { 37 | node.declarationList.forEachChild((vd: VariableDeclaration) => { 38 | output[vd.name.getText()] = vd.initializer 39 | }) 40 | 41 | return output 42 | }, 43 | {} as Record 44 | ) 45 | 46 | const configAST = variableDeclarationMap["config"] 47 | 48 | if (!configAST || !isObjectLiteralExpression(configAST)) { 49 | return null 50 | } 51 | 52 | const config = configAST.properties.reduce((output, node) => { 53 | if (node.getChildCount() < 3) { 54 | return output 55 | } 56 | 57 | const [keyNode, _, valueNode] = node.getChildren() 58 | 59 | const key = keyNode.getText() 60 | 61 | try { 62 | if (valueNode.kind === SyntaxKind.Identifier) { 63 | output[key] = parseAst(variableDeclarationMap[valueNode.getText()]) 64 | } else { 65 | output[key] = parseAst(valueNode) 66 | } 67 | } catch (error) { 68 | eLog(error) 69 | } 70 | 71 | return output 72 | }, {} as ManifestContentScript) 73 | 74 | vLog("Parsed config:", config) 75 | 76 | return { 77 | config 78 | } 79 | } catch (error) { 80 | vLog(error) 81 | return null 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/extension-devtools/get-bundle-config.ts: -------------------------------------------------------------------------------- 1 | import { getFlagMap } from "~features/helpers/flag" 2 | 3 | export const getBundleConfig = () => { 4 | const flagMap = getFlagMap() 5 | const { target, tag } = flagMap 6 | const [browser, manifestVersion] = target.split("-") 7 | 8 | // Potential runtime config here 9 | return { 10 | tag, 11 | target, 12 | browser, 13 | manifestVersion 14 | } 15 | } 16 | 17 | export type PlasmoBundleConfig = ReturnType 18 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/extension-devtools/git-ignore.ts: -------------------------------------------------------------------------------- 1 | export const generateGitIgnore = () => ` 2 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | #cache 13 | .turbo 14 | .next 15 | .vercel 16 | 17 | # misc 18 | .DS_Store 19 | *.pem 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | .pnpm-debug.log* 26 | 27 | 28 | # local env files 29 | .env* 30 | 31 | out/ 32 | build/ 33 | dist/ 34 | 35 | # plasmo - https://www.plasmo.com 36 | .plasmo 37 | 38 | # bpp - https://github.com/marketplace/actions/browser-platform-publisher 39 | keys.json 40 | 41 | # typescript 42 | .tsbuildinfo 43 | ` 44 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/extension-devtools/parse-ast.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Plasmo Corp, foss@plasmo.com, MIT Licensed 3 | * --- 4 | * Adapted from https://github.com/dword-design/ts-ast-to-literal/blob/master/src/index.js 5 | * Copyright (c) Sebastian Landwehr info@sebastianlandwehr.com, MIT licensed 6 | */ 7 | import typescript, { 8 | type ArrayLiteralExpression, 9 | type Identifier, 10 | type LiteralExpression, 11 | type Node, 12 | type ObjectLiteralExpression, 13 | type PropertyAssignment 14 | } from "typescript" 15 | 16 | const { SyntaxKind } = typescript 17 | 18 | export const parseAst = (node: Node) => { 19 | switch (node.kind) { 20 | case SyntaxKind.StringLiteral: 21 | return (node as LiteralExpression).text 22 | case SyntaxKind.TrueKeyword: 23 | return true 24 | case SyntaxKind.FalseKeyword: 25 | return false 26 | case SyntaxKind.NullKeyword: 27 | return null 28 | case SyntaxKind.NumericLiteral: 29 | return parseFloat((node as LiteralExpression).text) 30 | case SyntaxKind.ArrayLiteralExpression: 31 | return (node as ArrayLiteralExpression).elements 32 | .filter((node) => node.kind !== SyntaxKind.SpreadElement) 33 | .map(parseAst) 34 | case SyntaxKind.ObjectLiteralExpression: 35 | return (node as ObjectLiteralExpression).properties 36 | .filter( 37 | (property) => 38 | property.kind === SyntaxKind.PropertyAssignment && 39 | (property.name.kind === SyntaxKind.Identifier || 40 | property.name.kind === SyntaxKind.StringLiteral) 41 | ) 42 | .map((property: PropertyAssignment) => [ 43 | (property.name as Identifier).escapedText || 44 | (property.name as LiteralExpression).text, 45 | parseAst(property.initializer) 46 | ]) 47 | .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) 48 | default: 49 | return undefined 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/extension-devtools/strip-underscore.ts: -------------------------------------------------------------------------------- 1 | import { readdir, readFile, rename, stat, writeFile } from "fs/promises" 2 | import { join, resolve } from "path" 3 | 4 | const stripFileUnderscore = async (filePath: string) => { 5 | const fileContents = await readFile(resolve(filePath), "utf8") 6 | const newFileContents = fileContents.replace(/\/_/g, "/") 7 | await writeFile(filePath, newFileContents, "utf8") 8 | } 9 | 10 | export const stripUnderscore = async (dir = "") => { 11 | const entries = await readdir(dir) 12 | 13 | for (const entry of entries) { 14 | const entryPath = join(dir, entry) 15 | const entryStat = await stat(entryPath) 16 | if (entryStat.isDirectory()) { 17 | const newPath = entryPath.replace("/_", "/") 18 | await rename(entryPath, newPath) 19 | await stripUnderscore(newPath) 20 | } else { 21 | const newPath = entryPath.replace("/_", "/") 22 | await rename(entryPath, newPath) 23 | await stripFileUnderscore(newPath) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/extension-devtools/template-path.ts: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from "path" 2 | import { fileURLToPath } from "url" 3 | 4 | export const getTemplatePath = () => { 5 | const packagePath = dirname(fileURLToPath(import.meta.url)) 6 | 7 | const templatePath = resolve(packagePath, "..", "templates") 8 | const staticTemplatePath = resolve(templatePath, "static") 9 | 10 | const initTemplatePackagePath = resolve( 11 | require.resolve("@plasmohq/init"), 12 | ".." 13 | ) 14 | 15 | const initTemplatePath = resolve(initTemplatePackagePath, "templates") 16 | const initEntryPath = resolve(initTemplatePackagePath, "entries") 17 | const bppYaml = resolve(initTemplatePackagePath, "bpp.yml") 18 | 19 | return { 20 | templatePath, 21 | initTemplatePath, 22 | initEntryPath, 23 | staticTemplatePath, 24 | bppYaml 25 | } 26 | } 27 | 28 | export type TemplatePath = ReturnType 29 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/extension-devtools/tsconfig.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "fs/promises" 2 | import { resolve } from "path" 3 | import { outputFile, outputJson } from "fs-extra" 4 | import json5 from "json5" 5 | 6 | import { MESSAGING_DECLARATION } from "~features/background-service-worker/bgsw-messaging-declaration" 7 | import { PROCESS_ENV_DECLARATION } from "~features/env/env-declaration" 8 | import type { CommonPath } from "~features/extension-devtools/common-path" 9 | 10 | const DECLARATION_FILEPATH = `.plasmo/index.d.ts` 11 | 12 | const INDEX_DECLARATION_CODE = [PROCESS_ENV_DECLARATION, MESSAGING_DECLARATION] 13 | .map((e) => `import "./${e}"`) 14 | .join("\n") 15 | 16 | export const addDeclarationConfig = async ( 17 | commonPath: CommonPath, 18 | filePath: string 19 | ) => { 20 | const tsconfigFilePath = resolve(commonPath.projectDirectory, "tsconfig.json") 21 | 22 | const tsconfigFile = await readFile(tsconfigFilePath, "utf8") 23 | const tsconfig = json5.parse(tsconfigFile) 24 | const includeSet = new Set(tsconfig.include) 25 | 26 | if (includeSet.has(filePath)) { 27 | return 28 | } 29 | 30 | tsconfig.include = [filePath, ...includeSet] 31 | 32 | await outputJson(tsconfigFilePath, tsconfig, { 33 | spaces: 2 34 | }) 35 | } 36 | 37 | export const outputIndexDeclaration = async (commonPath: CommonPath) => { 38 | const declarationFilePath = resolve( 39 | commonPath.projectDirectory, 40 | DECLARATION_FILEPATH 41 | ) 42 | 43 | await Promise.all([ 44 | outputFile(declarationFilePath, INDEX_DECLARATION_CODE), 45 | addDeclarationConfig(commonPath, DECLARATION_FILEPATH) 46 | ]) 47 | } 48 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/extra/cache-busting.ts: -------------------------------------------------------------------------------- 1 | import { lstat } from "fs/promises" 2 | import { resolve } from "path" 3 | import { emptyDir, ensureDir } from "fs-extra" 4 | 5 | import { isAccessible } from "@plasmo/utils/fs" 6 | import { vLog } from "@plasmo/utils/logging" 7 | 8 | import type { CommonPath } from "~features/extension-devtools/common-path" 9 | 10 | export async function cleanUpDotPlasmo({ 11 | dotPlasmoDirectory, 12 | cacheDirectory 13 | }: CommonPath) { 14 | await emptyDir(dotPlasmoDirectory) 15 | await ensureDir(cacheDirectory) 16 | } 17 | 18 | export async function cleanUpLargeCache(commonPath: CommonPath) { 19 | const parcelCacheDbFilePath = resolve( 20 | commonPath.cacheDirectory, 21 | "parcel", 22 | "data.mdb" 23 | ) 24 | 25 | const hasCache = await isAccessible(parcelCacheDbFilePath) 26 | 27 | if (!hasCache) { 28 | return 29 | } 30 | 31 | const cacheDbFileSize = (await lstat(parcelCacheDbFilePath, { bigint: true })) 32 | .size 33 | 34 | const sizeInMB = cacheDbFileSize / 1024n ** 2n 35 | const sizeInGB = Number(sizeInMB) / 1024 36 | 37 | // TODO: calculate the limit based on some heuristic around the size of the project instead of a fixed value. 38 | const cacheLimitGB = 1.47 39 | 40 | if (sizeInGB > cacheLimitGB) { 41 | vLog(`Busting large build cache, size: ${sizeInGB.toFixed(2)} GB`) 42 | await cleanUpDotPlasmo(commonPath) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/helpers/crypto.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "crypto" 2 | 3 | /** 4 | * Fast hash for local file revving 5 | * DO NOT USE FOR SENSITIVE PURPOSES 6 | * md5 is good enough for file-revving: https://github.com/sindresorhus/rev-hash 7 | */ 8 | export const getRevHash = (buff: Buffer) => 9 | createHash("md5").update(buff).digest("hex").slice(0, 18) 10 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/helpers/flag.ts: -------------------------------------------------------------------------------- 1 | import { kebabCase } from "change-case" 2 | 3 | import { getFlag } from "@plasmo/utils/flags" 4 | 5 | export const getFlagMap = () => { 6 | const srcPath = getFlag("--src-path") || process.env.PLASMO_SRC_PATH || "src" 7 | 8 | const buildPath = 9 | getFlag("--build-path") || process.env.PLASMO_BUILD_PATH || "build" 10 | 11 | const tag = 12 | getFlag("--tag") || 13 | process.env.PLASMO_TAG || 14 | (process.env.NODE_ENV === "production" ? "prod" : "dev") 15 | 16 | const target = kebabCase( 17 | getFlag("--target") || process.env.PLASMO_TARGET || "chrome-mv3" 18 | ) 19 | 20 | const [browser, manifestVersion] = target.split("-") 21 | 22 | const entry = getFlag("--entry") || "popup" 23 | 24 | const envPath = getFlag("--env") 25 | 26 | return { 27 | browser, 28 | manifestVersion, 29 | tag, 30 | srcPath, 31 | buildPath, 32 | target, 33 | entry, 34 | envPath 35 | } 36 | } 37 | 38 | const DEV_BUILD_COMMON_ARGS = ` --target=[string] set the target (default: chrome-mv3) 39 | --tag=[string] set the build tag (default: dev or prod depending on NODE_ENV) 40 | --src-path=[path] set the source path relative to project root (default: src) 41 | --build-path=[path] set the build path relative to project root (default: build) 42 | --entry=[name] entry point name (default: popup) 43 | --env=[path] relative path to top-level env file 44 | --no-cs-reload disable content script auto reloading` 45 | 46 | export const flagHelp = ` 47 | 48 | init 49 | 50 | --entry=[name] entry files (default: popup) 51 | --with- use an example template 52 | 53 | dev 54 | 55 | ${DEV_BUILD_COMMON_ARGS} 56 | 57 | build 58 | 59 | ${DEV_BUILD_COMMON_ARGS} 60 | --zip zip the build output 61 | ` 62 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/helpers/loading-animation.ts: -------------------------------------------------------------------------------- 1 | const LOADING_TEXT = "🔄 Building" 2 | const state = { 3 | loadingInterval: null as NodeJS.Timeout | null, 4 | isLoading: false, 5 | dotCount: 0 6 | } 7 | 8 | export const startLoading = () => { 9 | if (state.isLoading) { 10 | return 11 | } 12 | state.isLoading = true 13 | process.stdout.write(LOADING_TEXT) 14 | state.loadingInterval = setInterval(() => { 15 | state.dotCount = (state.dotCount + 1) % 4 16 | let dotString = state.dotCount === 0 ? " " : ".".repeat(state.dotCount) 17 | process.stdout.write(`\r${LOADING_TEXT}${dotString}`) 18 | }, 400) 19 | } 20 | 21 | export const stopLoading = () => { 22 | if (!state.isLoading) { 23 | return 24 | } 25 | state.isLoading = false 26 | if (state.loadingInterval) { 27 | clearInterval(state.loadingInterval) 28 | state.loadingInterval = null 29 | } 30 | // Clear the loading text 31 | process.stdout.write("\r" + " ".repeat(20) + "\r") 32 | } 33 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/helpers/package-manager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Forked from https://github.com/vercel/next.js/blob/canary/packages/create-next-app/helpers/get-pkg-manager.ts 3 | */ 4 | import spawnAsync from "@expo/spawn-async" 5 | import semver from "semver" 6 | 7 | export type PackageManager = "npm" | "pnpm" | "yarn" 8 | 9 | export type PackageManagerInfo = { 10 | name: PackageManager 11 | version?: string 12 | } 13 | 14 | async function getPMInfo(name: PackageManager): Promise { 15 | const data = await spawnAsync(name, ["--version"]) 16 | const version = semver.valid(data.stdout.trim()) || undefined 17 | return { name, version } 18 | } 19 | 20 | export async function getPackageManager(): Promise { 21 | try { 22 | const userAgent = process.env.npm_config_user_agent 23 | 24 | if (userAgent) { 25 | if (userAgent.startsWith("yarn")) { 26 | return { name: "yarn" } 27 | } else if (userAgent.startsWith("pnpm")) { 28 | return { name: "pnpm" } 29 | } 30 | } 31 | try { 32 | return await getPMInfo("pnpm") 33 | } catch { 34 | return await getPMInfo("yarn") 35 | } 36 | } catch { 37 | return await getPMInfo("npm") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/helpers/print.ts: -------------------------------------------------------------------------------- 1 | import { cLog } from "@plasmo/utils/logging" 2 | 3 | import { validCommandList } from "~commands" 4 | import { flagHelp } from "~features/helpers/flag" 5 | 6 | export const printHeader = () => { 7 | console.log(`🟣 Plasmo v${process.env.APP_VERSION}`) 8 | 9 | console.log("🔴 The Browser Extension Framework") 10 | } 11 | 12 | export const printHelp = () => { 13 | cLog("🟠 CMDS", validCommandList.join(" | ")) 14 | 15 | cLog("🟡 OPTS", flagHelp) 16 | } 17 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/helpers/prompt.ts: -------------------------------------------------------------------------------- 1 | import inquirer from "inquirer" 2 | 3 | export const quickPrompt = async (label = "", defaultValue = "") => { 4 | const { data } = await inquirer.prompt({ 5 | name: "data", 6 | prefix: "🟡", 7 | message: label, 8 | default: defaultValue 9 | }) 10 | 11 | return data as string 12 | } 13 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/helpers/traverse.ts: -------------------------------------------------------------------------------- 1 | import { iLog } from "@plasmo/utils/logging" 2 | 3 | const defaultTransformer = (target: any) => 4 | iLog({ 5 | target 6 | }) 7 | 8 | /** 9 | * Traverse a target object and apply a transformer function to each value. 10 | * Retains only defined values. 11 | */ 12 | export const definedTraverse = ( 13 | target: any, 14 | transformer = defaultTransformer 15 | ): any => { 16 | if (Array.isArray(target)) { 17 | return target 18 | .map((item) => definedTraverse(item, transformer)) 19 | .filter((i) => i !== undefined) 20 | } else if (typeof target === "object") { 21 | const result = {} as any 22 | 23 | for (const key in target) { 24 | if (target.hasOwnProperty(key)) { 25 | result[key] = definedTraverse(target[key], transformer) 26 | if (result[key] === undefined) { 27 | delete result[key] 28 | } 29 | } 30 | } 31 | 32 | if (Object.keys(result).length === 0) { 33 | return undefined 34 | } 35 | 36 | return result 37 | } else { 38 | return transformer(target) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/manifest-factory/create-manifest.ts: -------------------------------------------------------------------------------- 1 | import { find } from "@plasmo/utils/array" 2 | import { isAccessible } from "@plasmo/utils/fs" 3 | import { vLog, wLog } from "@plasmo/utils/logging" 4 | 5 | import { updateBgswEntry } from "~features/background-service-worker/update-bgsw-entry" 6 | import type { PlasmoBundleConfig } from "~features/extension-devtools/get-bundle-config" 7 | 8 | import { PlasmoExtensionManifestMV2 } from "./mv2" 9 | import { PlasmoExtensionManifestMV3 } from "./mv3" 10 | 11 | export async function createManifest(bundleConfig: PlasmoBundleConfig) { 12 | vLog("Creating Manifest Factory...") 13 | const plasmoManifest = 14 | bundleConfig.manifestVersion === "mv3" 15 | ? new PlasmoExtensionManifestMV3(bundleConfig) 16 | : new PlasmoExtensionManifestMV2(bundleConfig) 17 | 18 | await plasmoManifest.startup() 19 | 20 | const { 21 | contentIndexList, 22 | sandboxIndexList, 23 | tabsDirectory, 24 | sandboxesDirectory 25 | } = plasmoManifest.projectPath 26 | 27 | const [contentIndex, sandboxIndex] = await Promise.all( 28 | [contentIndexList, sandboxIndexList].map((l) => find(l, isAccessible)) 29 | ) 30 | 31 | const initResults = await Promise.all([ 32 | plasmoManifest.scaffolder.init(), 33 | plasmoManifest.togglePage(sandboxIndex, true), 34 | 35 | plasmoManifest.toggleContentScript(contentIndex, true), 36 | plasmoManifest.addContentScriptsDirectory(), 37 | 38 | plasmoManifest.addPagesDirectory(tabsDirectory), 39 | plasmoManifest.addPagesDirectory(sandboxesDirectory) 40 | ]) 41 | 42 | // BGSW needs to check CS set for main world 43 | initResults.push(await updateBgswEntry(plasmoManifest)) 44 | 45 | const hasEntrypoints = initResults.flat() 46 | 47 | if (!hasEntrypoints.includes(true)) { 48 | wLog("Unable to find any entry files. The extension might be empty") 49 | } 50 | 51 | const [hasPopup, hasOptions, hasNewtab, hasDevtools, hasSidePanel] = 52 | hasEntrypoints 53 | 54 | plasmoManifest 55 | .togglePopup(hasPopup) 56 | .toggleOptions(hasOptions) 57 | .toggleNewtab(hasNewtab) 58 | .toggleDevtools(hasDevtools) 59 | .toggleSidePanel(hasSidePanel) 60 | 61 | await plasmoManifest.write(true) 62 | 63 | return plasmoManifest 64 | } 65 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/project-creator/get-raw-name.ts: -------------------------------------------------------------------------------- 1 | import { createQuestId } from "mnemonic-id" 2 | 3 | import { getNonFlagArgvs } from "@plasmo/utils/argv" 4 | import { vLog } from "@plasmo/utils/logging" 5 | 6 | import { quickPrompt } from "~features/helpers/prompt" 7 | 8 | export const getRawName = async () => { 9 | const [rawNameNonInteractive] = getNonFlagArgvs("init") 10 | 11 | if (!!rawNameNonInteractive) { 12 | vLog("Using user-provided name:", rawNameNonInteractive) 13 | return rawNameNonInteractive 14 | } 15 | 16 | vLog("Prompting for the extension name") 17 | return await quickPrompt("Extension name:", createQuestId()) 18 | } 19 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/project-creator/git-init.ts: -------------------------------------------------------------------------------- 1 | import spawnAsync, { type SpawnOptions } from "@expo/spawn-async" 2 | 3 | import { isAccessible } from "@plasmo/utils/fs" 4 | import { iLog, vLog, wLog } from "@plasmo/utils/logging" 5 | 6 | import type { CommonPath } from "~features/extension-devtools/common-path" 7 | 8 | const gitInitAddCommit = async (root: string) => { 9 | const { default: chalk } = await import("chalk") 10 | const commonOpt: SpawnOptions = { cwd: root, ignoreStdio: true } 11 | 12 | try { 13 | vLog("Checking if the root is a git repository") 14 | await spawnAsync("git", ["rev-parse", "--is-inside-work-tree"], commonOpt) 15 | vLog(`${root} is a git repository, bailing ${chalk.bold("git init")}`) 16 | return false 17 | } catch (e: any) { 18 | if (e.code === "ENOENT") { 19 | throw new Error("Unable to initialize git repo. `git` not in PATH.") 20 | } 21 | } 22 | 23 | iLog("Initializing git project...") 24 | 25 | await spawnAsync("git", ["init"], commonOpt) 26 | 27 | await spawnAsync("git", ["add", "--all"], commonOpt) 28 | 29 | await spawnAsync( 30 | "git", 31 | ["commit", "-m", "Created a new Plasmo extension"], 32 | commonOpt 33 | ) 34 | vLog("Added all files to git and created the initial commit.") 35 | 36 | return true 37 | } 38 | 39 | export async function gitInit( 40 | commonPath: CommonPath, 41 | root: string 42 | ): Promise { 43 | if (!(await isAccessible(commonPath.gitIgnorePath))) { 44 | return false 45 | } 46 | 47 | try { 48 | return await gitInitAddCommit(root) 49 | } catch (error: any) { 50 | wLog(error.message) 51 | return false 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/project-creator/install-dependencies.ts: -------------------------------------------------------------------------------- 1 | import spawnAsync from "@expo/spawn-async" 2 | 3 | import { iLog, wLog } from "@plasmo/utils/logging" 4 | 5 | import type { PackageManagerInfo } from "~features/helpers/package-manager" 6 | 7 | export const installDependencies = async ( 8 | projectDirectory: string, 9 | packageManager: PackageManagerInfo 10 | ) => { 11 | try { 12 | iLog("Installing dependencies...") 13 | await spawnAsync(packageManager.name, ["install"], { 14 | cwd: projectDirectory, 15 | stdio: "inherit" 16 | }) 17 | } catch (error: any) { 18 | wLog(error.message) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cli/plasmo/src/features/project-creator/print-ready.ts: -------------------------------------------------------------------------------- 1 | import { sLog } from "@plasmo/utils/logging" 2 | 3 | import type { CommonPath } from "~features/extension-devtools/common-path" 4 | import type { PackageManagerInfo } from "~features/helpers/package-manager" 5 | 6 | export const printReady = async ( 7 | projectDirectory: string, 8 | currentDirectory: string, 9 | commonPath: CommonPath, 10 | packageManager: PackageManagerInfo 11 | ) => { 12 | const { default: chalk } = await import("chalk") 13 | 14 | sLog( 15 | "Your extension is ready in: ", 16 | chalk.yellowBright(projectDirectory), 17 | `\n\n To start hacking, run:\n\n`, 18 | projectDirectory === currentDirectory 19 | ? "" 20 | : ` cd ${commonPath.packageName}\n`, 21 | ` ${packageManager.name} ${ 22 | packageManager.name === "npm" ? "run dev" : "dev" 23 | }\n`, 24 | "\n Visit https://docs.plasmo.com for documentation and more examples." 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /cli/plasmo/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { argv, exit, versions } from "process" 3 | import semver from "semver" 4 | 5 | import { ErrorMessage } from "@plasmo/constants/error" 6 | import { verbose } from "@plasmo/utils/flags" 7 | import { eLog, vLog } from "@plasmo/utils/logging" 8 | 9 | import { runMap, validCommandSet, type ValidCommand } from "~commands" 10 | import { printHeader, printHelp } from "~features/helpers/print" 11 | 12 | async function defaultMode() { 13 | printHeader() 14 | 15 | printHelp() 16 | } 17 | 18 | async function main() { 19 | try { 20 | // In case someone pasted an essay into the cli 21 | if (argv.length > 10) { 22 | throw new Error(ErrorMessage.TooManyArg) 23 | } 24 | 25 | if (semver.major(versions.node) < 16) { 26 | throw new Error("Node version must be >= 16") 27 | } 28 | 29 | process.env.VERBOSE = verbose ? "true" : "false" 30 | 31 | // Setting startup policy/daemon 32 | const mode = argv.find((arg) => 33 | validCommandSet.has(arg as ValidCommand) 34 | ) as ValidCommand 35 | 36 | if (mode in runMap) { 37 | vLog("Running command:", mode) 38 | 39 | const { default: runner } = await runMap[mode]() 40 | 41 | await runner() 42 | } else { 43 | vLog("Running default mode") 44 | await defaultMode() 45 | } 46 | } catch (e) { 47 | eLog((e as Error)?.message || ErrorMessage.Unknown) 48 | vLog(e?.stack) 49 | exit(1) 50 | } 51 | } 52 | 53 | main() 54 | 55 | process.on("SIGINT", () => exit(0)) 56 | process.on("SIGTERM", () => exit(0)) 57 | -------------------------------------------------------------------------------- /cli/plasmo/templates/plasmo.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | interface ProcessEnv { 3 | NODE_ENV: "development" | "production" 4 | 5 | PLASMO_BROWSER?: 6 | | "arc" 7 | | "brave" 8 | | "chrome" 9 | | "chromium" 10 | | "edge" 11 | | "firefox" 12 | | "gecko" 13 | | "island" 14 | | "opera" 15 | | "plasmo" 16 | | "safari" 17 | | "sigmaos" 18 | | "tor" 19 | | "vivaldi" 20 | | "waterfox" 21 | | "yandex" 22 | 23 | PLASMO_MANIFEST_VERSION?: "mv2" | "mv3" 24 | 25 | PLASMO_TARGET?: `${ProcessEnv["PLASMO_BROWSER"]}-${ProcessEnv["PLASMO_MANIFEST_VERSION"]}` 26 | 27 | PLASMO_TAG?: string 28 | } 29 | } 30 | 31 | declare module "*.module.css" 32 | declare module "*.module.less" 33 | declare module "*.module.scss" 34 | declare module "*.module.sass" 35 | declare module "*.module.styl" 36 | declare module "*.module.pcss" 37 | 38 | declare module "react:*.svg" { 39 | import type { FunctionComponent, SVGProps } from "react" 40 | 41 | const value: FunctionComponent> 42 | export default value 43 | } 44 | 45 | declare module "*.gql" 46 | declare module "*.graphql" 47 | 48 | declare module "react:*" 49 | 50 | declare module "https:*" 51 | 52 | declare module "url:*" { 53 | const value: string 54 | export default value 55 | } 56 | 57 | declare module "data-text:*" { 58 | const value: string 59 | export default value 60 | } 61 | 62 | declare module "data-base64:*" { 63 | const value: string 64 | export default value 65 | } 66 | 67 | declare module "data-env:*" { 68 | const value: string 69 | export default value 70 | } 71 | 72 | declare module "data-text-env:*" { 73 | const value: string 74 | export default value 75 | } 76 | 77 | declare module "raw:*" { 78 | const value: string 79 | export default value 80 | } 81 | 82 | declare module "raw-env:*" { 83 | const value: string 84 | export default value 85 | } 86 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/background/index.ts: -------------------------------------------------------------------------------- 1 | import "./messaging" 2 | import "~background" 3 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/common/csui-container-react.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import type { PlasmoCSUIContainerProps } from "~type" 4 | 5 | export const OverlayCSUIContainer = (props: PlasmoCSUIContainerProps) => { 6 | const [top, setTop] = React.useState(0) 7 | const [left, setLeft] = React.useState(0) 8 | 9 | React.useEffect(() => { 10 | // Handle overlay repositioning 11 | if (props.anchor.type !== "overlay") { 12 | return 13 | } 14 | 15 | const updatePosition = async () => { 16 | const rect = props.anchor.element?.getBoundingClientRect() 17 | if (!rect) { 18 | return 19 | } 20 | 21 | const pos = { 22 | left: rect.left + window.scrollX, 23 | top: rect.top + window.scrollY 24 | } 25 | 26 | setLeft(pos.left) 27 | setTop(pos.top) 28 | } 29 | 30 | updatePosition() 31 | 32 | const unwatch = props.watchOverlayAnchor?.(updatePosition) 33 | window.addEventListener("scroll", updatePosition) 34 | window.addEventListener("resize", updatePosition) 35 | 36 | return () => { 37 | if (typeof unwatch === "function") { 38 | unwatch() 39 | } 40 | window.removeEventListener("scroll", updatePosition) 41 | window.removeEventListener("resize", updatePosition) 42 | } 43 | }, [props.anchor.element]) 44 | 45 | return ( 46 |
55 | {props.children} 56 |
57 | ) 58 | } 59 | 60 | export const InlineCSUIContainer = (props: PlasmoCSUIContainerProps) => ( 61 |
70 | {props.children} 71 |
72 | ) 73 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/common/csui-container-vanilla.tsx: -------------------------------------------------------------------------------- 1 | import type { PlasmoCSUIContainerProps } from "~type" 2 | 3 | export const createOverlayCSUIContainer = (props: PlasmoCSUIContainerProps) => { 4 | const container = document.createElement("div") 5 | container.className = "plasmo-csui-container" 6 | container.id = props.id 7 | 8 | container.style.cssText = ` 9 | display: flex; 10 | position: relative; 11 | top: 0px; 12 | left: 0px; 13 | ` 14 | 15 | if (props.anchor.type === "overlay") { 16 | const updatePosition = async () => { 17 | const rect = props.anchor.element.getBoundingClientRect() 18 | 19 | if (!rect) { 20 | return 21 | } 22 | 23 | const pos = { 24 | left: rect.left + window.scrollX, 25 | top: rect.top + window.scrollY 26 | } 27 | 28 | container.style.top = `${pos.top}px` 29 | container.style.left = `${pos.left}px` 30 | } 31 | 32 | updatePosition() 33 | 34 | props.watchOverlayAnchor?.(updatePosition) 35 | window.addEventListener("scroll", updatePosition) 36 | window.addEventListener("resize", updatePosition) 37 | } 38 | 39 | return container 40 | } 41 | 42 | export const createInlineCSUIContainer = (props: PlasmoCSUIContainerProps) => { 43 | const container = document.createElement("div") 44 | container.className = "plasmo-csui-container" 45 | container.id = "plasmo-inline" 46 | 47 | container.style.cssText = ` 48 | display: flex; 49 | position: relative; 50 | top: 0px; 51 | left: 0px; 52 | ` 53 | 54 | return container 55 | } 56 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/common/react.ts: -------------------------------------------------------------------------------- 1 | import { Fragment, type FC, type ReactNode } from "react" 2 | 3 | export const getLayout = (RawImport: any): FC<{ children: ReactNode }> => 4 | typeof RawImport.Layout === "function" 5 | ? RawImport.Layout 6 | : typeof RawImport.getGlobalProvider === "function" 7 | ? RawImport.getGlobalProvider() 8 | : Fragment 9 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/common/vue.ts: -------------------------------------------------------------------------------- 1 | globalThis.__VUE_OPTIONS_API__ = true 2 | globalThis.__VUE_PROD_DEVTOOLS__ = process.env.NODE_ENV !== "production" 3 | globalThis.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = false 4 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/react17/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | __plasmo_static_index_title__ 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/react17/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import * as ReactDOM from "react-dom" 3 | 4 | import { getLayout } from "@plasmo-static-common/react" 5 | 6 | // @ts-ignore 7 | import * as Component from "__plasmo_import_module__" 8 | 9 | let __plasmoRoot: HTMLElement = null 10 | 11 | document.addEventListener("DOMContentLoaded", () => { 12 | if (!!__plasmoRoot) { 13 | return 14 | } 15 | 16 | const Layout = getLayout(Component) 17 | 18 | __plasmoRoot = document.getElementById("__plasmo") 19 | 20 | ReactDOM.render( 21 | 22 | 23 | , 24 | __plasmoRoot 25 | ) 26 | }) 27 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/react18/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | __plasmo_static_index_title__ 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/react18/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { createRoot } from "react-dom/client" 3 | 4 | import { getLayout } from "@plasmo-static-common/react" 5 | 6 | // @ts-ignore 7 | import * as Component from "__plasmo_import_module__" 8 | 9 | let __plasmoRoot: HTMLElement = null 10 | 11 | document.addEventListener("DOMContentLoaded", () => { 12 | if (!!__plasmoRoot) { 13 | return 14 | } 15 | 16 | __plasmoRoot = document.getElementById("__plasmo") 17 | 18 | const root = createRoot(__plasmoRoot) 19 | 20 | const Layout = getLayout(Component) 21 | 22 | root.render( 23 | 24 | 25 | 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/react19/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | __plasmo_static_index_title__ 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/react19/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { createRoot } from "react-dom/client" 3 | 4 | import { getLayout } from "@plasmo-static-common/react" 5 | 6 | // @ts-ignore 7 | import * as Component from "__plasmo_import_module__" 8 | 9 | let __plasmoRoot: HTMLElement = null 10 | 11 | document.addEventListener("DOMContentLoaded", () => { 12 | if (!!__plasmoRoot) { 13 | return 14 | } 15 | 16 | __plasmoRoot = document.getElementById("__plasmo") 17 | 18 | const root = createRoot(__plasmoRoot) 19 | 20 | const Layout = getLayout(Component) 21 | 22 | root.render( 23 | 24 | 25 | 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/svelte4/content-script-ui-mount.ts: -------------------------------------------------------------------------------- 1 | import { createAnchorObserver, createRender } from "@plasmo-static-common/csui" 2 | import { 3 | createInlineCSUIContainer, 4 | createOverlayCSUIContainer 5 | } from "@plasmo-static-common/csui-container-vanilla" 6 | 7 | import type { 8 | PlasmoCSUI, 9 | PlasmoCSUIAnchor, 10 | PlasmoCSUIHTMLContainer 11 | } from "~type" 12 | 13 | // @ts-ignore 14 | import * as RawMount from "__plasmo_mount_content_script__" 15 | 16 | // Escape parcel's static analyzer 17 | const Mount = RawMount as PlasmoCSUI 18 | 19 | const observer = createAnchorObserver(Mount) 20 | 21 | const render = createRender( 22 | Mount, 23 | [createInlineCSUIContainer, createOverlayCSUIContainer], 24 | observer?.mountState, 25 | async (anchor, rootContainer) => { 26 | switch (anchor.type) { 27 | case "inline": { 28 | const mountPoint = createInlineCSUIContainer({ anchor }) 29 | rootContainer.appendChild(mountPoint) 30 | new Mount.default({ 31 | target: mountPoint, 32 | props: { 33 | anchor 34 | } 35 | }) 36 | break 37 | } 38 | case "overlay": { 39 | const targetList = observer?.mountState.overlayTargetList || [ 40 | anchor.element 41 | ] 42 | 43 | targetList.forEach((target, i) => { 44 | const id = `plasmo-overlay-${i}` 45 | const innerAnchor: PlasmoCSUIAnchor = { 46 | element: target, 47 | type: "overlay" 48 | } 49 | 50 | const mountPoint = createOverlayCSUIContainer({ 51 | id, 52 | anchor: innerAnchor, 53 | watchOverlayAnchor: Mount.watchOverlayAnchor 54 | }) 55 | 56 | rootContainer.appendChild(mountPoint) 57 | new Mount.default({ 58 | target: mountPoint, 59 | props: { 60 | anchor: innerAnchor 61 | } 62 | }) 63 | }) 64 | break 65 | } 66 | } 67 | } 68 | ) 69 | 70 | if (!!observer) { 71 | observer.start(render) 72 | } else { 73 | render({ 74 | element: document.documentElement, 75 | type: "overlay" 76 | }) 77 | } 78 | 79 | if (typeof Mount.watch === "function") { 80 | Mount.watch({ 81 | observer, 82 | render 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/svelte4/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | __plasmo_static_index_title__ 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/svelte4/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import * as Component from "__plasmo_import_module__" 3 | 4 | let __plasmoRoot: HTMLElement = null 5 | 6 | document.addEventListener("DOMContentLoaded", () => { 7 | if (!!__plasmoRoot) { 8 | return 9 | } 10 | 11 | __plasmoRoot = document.getElementById("__plasmo") 12 | new Component.default({ 13 | target: __plasmoRoot 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/vanilla/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | __plasmo_static_index_title__ 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/vanilla/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import "__plasmo_import_module__" 3 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/vue3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | __plasmo_static_index_title__ 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /cli/plasmo/templates/static/vue3/index.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue" 2 | 3 | // @ts-ignore 4 | import * as Component from "__plasmo_import_module__" 5 | 6 | import "@plasmo-static-common/vue" 7 | 8 | document.addEventListener("DOMContentLoaded", () => { 9 | const app = createApp(Component.default) 10 | Component.default.prepare?.(app) 11 | app.mount("#__plasmo") 12 | }) 13 | -------------------------------------------------------------------------------- /cli/plasmo/templates/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Plasmo Extension", 4 | "files": ["./plasmo.d.ts"], 5 | "compilerOptions": { 6 | "strict": false, 7 | "skipLibCheck": true, 8 | 9 | "target": "esnext", 10 | "lib": ["dom", "dom.iterable", "esnext"], 11 | "allowJs": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "incremental": true, 15 | "esModuleInterop": true, 16 | "module": "esnext", 17 | "resolveJsonModule": true, 18 | "declaration": false, 19 | "declarationMap": false, 20 | "inlineSources": false, 21 | "moduleResolution": "node", 22 | "noUnusedLocals": false, 23 | "noUnusedParameters": false, 24 | "preserveWatchOutput": true, 25 | 26 | "verbatimModuleSyntax": true, 27 | "jsx": "preserve" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cli/plasmo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/framework.json", 3 | "include": [ 4 | "src/**/*.ts", 5 | "templates/plasmo.d.ts", 6 | "templates/static/**/*.ts", 7 | "templates/static/**/*.tsx" 8 | ], 9 | "exclude": ["dist", "node_modules"], 10 | "compilerOptions": { 11 | "outDir": "dist", 12 | "baseUrl": ".", 13 | "lib": ["es2022", "dom"], 14 | "jsx": "preserve", 15 | "paths": { 16 | "~*": ["./src/*"], 17 | "@plasmo-static-common/*": ["./templates/static/common/*"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /core/parcel-bundler/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-bundler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-bundler", 3 | "version": "0.5.6", 4 | "license": "MIT", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/PlasmoHQ/plasmo.git" 8 | }, 9 | "main": "dist/index.js", 10 | "source": "src/index.ts", 11 | "scripts": { 12 | "prepublishOnly": "pnpm build", 13 | "build": "tsup src/index.ts --minify --clean", 14 | "dev": "tsup src/index.ts --sourcemap --watch" 15 | }, 16 | "engines": { 17 | "node": ">= 16.0.0", 18 | "parcel": ">= 2.7.0" 19 | }, 20 | "dependencies": { 21 | "@parcel/core": "2.9.3", 22 | "@parcel/diagnostic": "2.9.3", 23 | "@parcel/graph": "2.9.3", 24 | "@parcel/hash": "2.9.3", 25 | "@parcel/plugin": "2.9.3", 26 | "@parcel/utils": "2.9.3", 27 | "nullthrows": "1.1.1" 28 | }, 29 | "devDependencies": { 30 | "@parcel/types": "2.9.3", 31 | "@plasmo/config": "workspace:*", 32 | "@plasmo/utils": "workspace:*", 33 | "tsup": "8.4.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/parcel-bundler/src/can-merge.ts: -------------------------------------------------------------------------------- 1 | export function canMerge(a, b) { 2 | // Bundles can be merged if they have the same type and environment, 3 | // unless they are explicitly marked as isolated or inline. 4 | return ( 5 | a.type === b.type && 6 | a.env.context === b.env.context && 7 | a.bundleBehavior == null && 8 | b.bundleBehavior == null 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /core/parcel-bundler/src/create-bundle.ts: -------------------------------------------------------------------------------- 1 | import type { Asset, BundleBehavior, Environment, Target } from "@parcel/types" 2 | import nullthrows from "nullthrows" 3 | 4 | import type { Bundle } from "./types" 5 | 6 | export function createBundle(opts: { 7 | uniqueKey?: string 8 | target: Target 9 | asset?: Asset 10 | env?: Environment 11 | type?: string 12 | needsStableName?: boolean 13 | bundleBehavior?: BundleBehavior | null | undefined 14 | }): Bundle { 15 | if (opts.asset == null) { 16 | return { 17 | uniqueKey: opts.uniqueKey, 18 | assets: new Set(), 19 | internalizedAssetIds: [], 20 | mainEntryAsset: null, 21 | size: 0, 22 | sourceBundles: new Set(), 23 | target: opts.target, 24 | type: nullthrows(opts.type), 25 | env: nullthrows(opts.env), 26 | needsStableName: Boolean(opts.needsStableName), 27 | bundleBehavior: opts.bundleBehavior 28 | } 29 | } 30 | 31 | let asset = nullthrows(opts.asset) 32 | return { 33 | uniqueKey: opts.uniqueKey, 34 | assets: new Set([asset]), 35 | internalizedAssetIds: [], 36 | mainEntryAsset: asset, 37 | size: asset.stats.size, 38 | sourceBundles: new Set(), 39 | target: opts.target, 40 | type: opts.type ?? asset.type, 41 | env: opts.env ?? asset.env, 42 | needsStableName: Boolean(opts.needsStableName), 43 | bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/parcel-bundler/src/get-entry-by-target.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import invariant from "assert" 3 | import type { Asset, Dependency, MutableBundleGraph } from "@parcel/types" 4 | import { DefaultMap } from "@parcel/utils" 5 | 6 | export function getEntryByTarget( 7 | bundleGraph: MutableBundleGraph 8 | ): DefaultMap> { 9 | // Find entries from assetGraph per target 10 | let targets: DefaultMap> = new DefaultMap( 11 | () => new Map() 12 | ) 13 | bundleGraph.traverse( 14 | { 15 | enter(node, context, actions) { 16 | if (node.type !== "asset") { 17 | return node 18 | } 19 | 20 | invariant( 21 | context != null && 22 | context.type === "dependency" && 23 | context.value.isEntry && 24 | context.value.target != null 25 | ) 26 | targets.get(context.value.target.distDir).set(node.value, context.value) 27 | actions.skipChildren() 28 | return node 29 | } 30 | }, 31 | undefined 32 | ) 33 | 34 | return targets 35 | } 36 | -------------------------------------------------------------------------------- /core/parcel-bundler/src/get-reachable-bundle-root.ts: -------------------------------------------------------------------------------- 1 | import nullthrows from "nullthrows" 2 | 3 | import type { BundleRoot } from "./types" 4 | 5 | export function getReachableBundleRoots(asset, graph): Array { 6 | return graph 7 | .getNodeIdsConnectedTo(graph.getNodeIdByContentKey(asset.id)) 8 | .map((nodeId) => nullthrows(graph.getNode(nodeId))) 9 | } 10 | -------------------------------------------------------------------------------- /core/parcel-bundler/src/remove-bundle.ts: -------------------------------------------------------------------------------- 1 | import invariant from "assert" 2 | import type { Graph, NodeId } from "@parcel/graph" 3 | import type { Asset, Dependency } from "@parcel/types" 4 | import type { DefaultMap } from "@parcel/utils" 5 | import nullthrows from "nullthrows" 6 | 7 | import type { Bundle } from "./types" 8 | 9 | export function removeBundle( 10 | bundleGraph: Graph, 11 | bundleId: NodeId, 12 | assetReference: DefaultMap> 13 | ) { 14 | let bundle = nullthrows(bundleGraph.getNode(bundleId)) 15 | invariant(bundle !== "root") 16 | 17 | for (let asset of bundle.assets) { 18 | assetReference.set( 19 | asset, 20 | assetReference.get(asset).filter((t) => !t.includes(bundle)) 21 | ) 22 | 23 | for (let sourceBundleId of bundle.sourceBundles) { 24 | let sourceBundle = nullthrows(bundleGraph.getNode(sourceBundleId)) 25 | invariant(sourceBundle !== "root") 26 | sourceBundle.assets.add(asset) 27 | sourceBundle.size += asset.stats.size 28 | } 29 | } 30 | 31 | bundleGraph.removeNode(bundleId) 32 | } 33 | -------------------------------------------------------------------------------- /core/parcel-bundler/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { ContentGraph, Graph, NodeId } from "@parcel/graph" 2 | import type { 3 | Asset, 4 | BundleBehavior, 5 | Dependency, 6 | Environment, 7 | Target 8 | } from "@parcel/types" 9 | import type { DefaultMap } from "@parcel/utils" 10 | 11 | import type { BitSet } from "./bit-set" 12 | 13 | type AssetId = string 14 | 15 | export type DependencyBundleGraph = ContentGraph< 16 | | { 17 | value: Bundle 18 | type: "bundle" 19 | } 20 | | { 21 | value: Dependency 22 | type: "dependency" 23 | }, 24 | number 25 | > 26 | // IdealGraph is the structure we will pass to decorate, 27 | // which mutates the assetGraph into the bundleGraph we would 28 | // expect from default bundler 29 | export type IdealGraph = { 30 | dependencyBundleGraph: DependencyBundleGraph 31 | bundleGraph: Graph 32 | bundleGroupBundleIds: Set 33 | assetReference: DefaultMap> 34 | } 35 | 36 | export type Bundle = { 37 | uniqueKey: string | null | undefined 38 | assets: Set 39 | internalizedAsset?: BitSet 40 | internalizedAssetIds: Array 41 | bundleBehavior?: BundleBehavior | null | undefined 42 | needsStableName: boolean 43 | mainEntryAsset: Asset | null | undefined 44 | size: number 45 | sourceBundles: Set 46 | target: Target 47 | env: Environment 48 | type: string 49 | } 50 | 51 | /* BundleRoot - An asset that is the main entry of a Bundle. */ 52 | export type BundleRoot = Asset 53 | 54 | export const dependencyPriorityEdges = { 55 | sync: 1, 56 | parallel: 2, 57 | lazy: 3 58 | } 59 | 60 | export type ResolvedBundlerConfig = { 61 | minBundles: number 62 | minBundleSize: number 63 | maxParallelRequests: number 64 | } 65 | -------------------------------------------------------------------------------- /core/parcel-bundler/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/framework", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-compressor-utf8/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-compressor-utf8/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-compressor-utf8", 3 | "version": "0.1.1", 4 | "description": "Plasmo UTF8 Compressor for Extension", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup --minify --clean", 12 | "dev": "tsup --sourcemap --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.8.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "@plasmo/utils": "workspace:*", 27 | "tsup": "8.4.0" 28 | }, 29 | "dependencies": { 30 | "@parcel/plugin": "2.9.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/parcel-compressor-utf8/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | */ 5 | import { Compressor } from "@parcel/plugin" 6 | 7 | import { Utf8Transform } from "./utf8-transform" 8 | 9 | export default new Compressor({ 10 | async compress({ stream }) { 11 | return { 12 | stream: stream.pipe(new Utf8Transform()) 13 | } 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /core/parcel-compressor-utf8/src/utf8-transform.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | */ 5 | import { Transform, type TransformCallback } from "stream" 6 | import { StringDecoder } from "string_decoder" 7 | 8 | class Utf8Transform extends Transform { 9 | private decoder = new StringDecoder("utf8") 10 | 11 | _transform(chunk: Buffer, _: BufferEncoding, callback: TransformCallback) { 12 | callback(null, this.transformChunk(this.decoder.write(chunk))) 13 | } 14 | 15 | _flush(callback: TransformCallback) { 16 | const remainder = this.decoder.end() 17 | remainder ? callback(null, this.transformChunk(remainder)) : callback() 18 | } 19 | 20 | private transformChunk(chunk: string, segmentSize: number = 1024): string { 21 | let result = "" 22 | for (let i = 0; i < chunk.length; i += segmentSize) { 23 | const endIndex = Math.min(i + segmentSize, chunk.length) 24 | const segment = chunk.substring(i, endIndex) 25 | result += this.transformSegment(segment) 26 | } 27 | return result 28 | } 29 | 30 | private transformSegment(segment: string): string { 31 | return Array.from(segment) 32 | .map((ch) => 33 | ch.charCodeAt(0) <= 0x7f 34 | ? ch 35 | : "\\u" + ("0000" + ch.charCodeAt(0).toString(16)).slice(-4) 36 | ) 37 | .join("") 38 | } 39 | } 40 | 41 | export { Utf8Transform } 42 | -------------------------------------------------------------------------------- /core/parcel-compressor-utf8/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/framework", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-compressor-utf8/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup" 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"] 5 | }) 6 | -------------------------------------------------------------------------------- /core/parcel-config/.gitignore: -------------------------------------------------------------------------------- 1 | run.json -------------------------------------------------------------------------------- /core/parcel-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-config", 3 | "version": "0.42.0", 4 | "license": "MIT", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/PlasmoHQ/plasmo.git" 8 | }, 9 | "main": "index.json", 10 | "dependencies": { 11 | "@parcel/compressor-raw": "2.9.3", 12 | "@parcel/config-default": "2.9.3", 13 | "@parcel/core": "2.9.3", 14 | "@parcel/optimizer-data-url": "2.9.3", 15 | "@parcel/reporter-bundle-buddy": "2.9.3", 16 | "@parcel/resolver-default": "2.9.3", 17 | "@parcel/runtime-js": "2.8.3", 18 | "@parcel/runtime-service-worker": "2.9.3", 19 | "@parcel/source-map": "2.1.1", 20 | "@parcel/transformer-babel": "2.9.3", 21 | "@parcel/transformer-css": "2.9.3", 22 | "@parcel/transformer-graphql": "2.9.3", 23 | "@parcel/transformer-inline-string": "2.9.3", 24 | "@parcel/transformer-js": "2.9.3", 25 | "@parcel/transformer-less": "2.9.3", 26 | "@parcel/transformer-postcss": "2.9.3", 27 | "@parcel/transformer-raw": "2.9.3", 28 | "@parcel/transformer-react-refresh-wrap": "2.9.3", 29 | "@parcel/transformer-sass": "2.9.3", 30 | "@parcel/transformer-svg-react": "2.9.3", 31 | "@parcel/transformer-worklet": "2.9.3", 32 | "@plasmohq/parcel-bundler": "workspace:*", 33 | "@plasmohq/parcel-compressor-utf8": "workspace:*", 34 | "@plasmohq/parcel-namer-manifest": "workspace:*", 35 | "@plasmohq/parcel-optimizer-encapsulate": "workspace:*", 36 | "@plasmohq/parcel-optimizer-es": "workspace:*", 37 | "@plasmohq/parcel-packager": "workspace:*", 38 | "@plasmohq/parcel-resolver": "workspace:*", 39 | "@plasmohq/parcel-resolver-post": "workspace:*", 40 | "@plasmohq/parcel-runtime": "workspace:*", 41 | "@plasmohq/parcel-transformer-inject-env": "workspace:*", 42 | "@plasmohq/parcel-transformer-inline-css": "workspace:*", 43 | "@plasmohq/parcel-transformer-manifest": "workspace:*", 44 | "@plasmohq/parcel-transformer-svelte": "workspace:*", 45 | "@plasmohq/parcel-transformer-vue": "workspace:*" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/parcel-core/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-core", 3 | "version": "0.1.11", 4 | "description": "Plasmo Parcel Core Fork", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "types": "src/types.ts", 10 | "scripts": { 11 | "prepublishOnly": "pnpm build", 12 | "build": "tsup --minify --clean", 13 | "dev": "tsup --sourcemap --watch" 14 | }, 15 | "author": "Plasmo Corp. ", 16 | "homepage": "https://docs.plasmo.com/", 17 | "engines": { 18 | "parcel": ">= 2.7.0" 19 | }, 20 | "license": "MIT", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/PlasmoHQ/plasmo.git" 24 | }, 25 | "devDependencies": { 26 | "@parcel/types": "2.8.3", 27 | "@plasmo/config": "workspace:*", 28 | "@plasmo/utils": "workspace:*", 29 | "tsup": "8.4.0" 30 | }, 31 | "dependencies": { 32 | "@parcel/cache": "2.9.3", 33 | "@parcel/core": "2.9.3", 34 | "@parcel/diagnostic": "2.9.3", 35 | "@parcel/events": "2.9.3", 36 | "@parcel/fs": "2.9.3", 37 | "@parcel/graph": "2.9.3", 38 | "@parcel/hash": "2.9.3", 39 | "@parcel/logger": "2.9.3", 40 | "@parcel/package-manager": "2.9.3", 41 | "@parcel/plugin": "2.9.3", 42 | "@parcel/source-map": "2.1.1", 43 | "@parcel/types": "2.9.3", 44 | "@parcel/utils": "2.9.3", 45 | "@parcel/watcher": "2.5.1", 46 | "@parcel/workers": "2.9.3", 47 | "abortcontroller-polyfill": "1.7.8", 48 | "nullthrows": "1.1.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/parcel-core/src/types.ts: -------------------------------------------------------------------------------- 1 | export type { InitialParcelOptions as ParcelOptions } from "@parcel/types" 2 | 3 | export { Parcel } from "./index" 4 | -------------------------------------------------------------------------------- /core/parcel-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/framework", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-core/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup" 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"] 5 | }) 6 | -------------------------------------------------------------------------------- /core/parcel-namer-manifest/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-namer-manifest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-namer-manifest", 3 | "version": "0.3.12", 4 | "description": "Plasmo Parcel Namer for Extension Manifest", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup src/index.ts --minify --clean", 12 | "dev": "tsup src/index.ts --sourcemap --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.7.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "tsup": "8.4.0" 27 | }, 28 | "dependencies": { 29 | "@parcel/core": "2.9.3", 30 | "@parcel/plugin": "2.9.3", 31 | "@parcel/types": "2.9.3", 32 | "@parcel/utils": "2.9.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/parcel-namer-manifest/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | */ 5 | import { Namer } from "@parcel/plugin" 6 | 7 | export default new Namer({ 8 | name({ bundle }) { 9 | const mainEntry = bundle.getMainEntry() 10 | 11 | if (!mainEntry) { 12 | return null 13 | } 14 | 15 | if ( 16 | bundle.type === "json" && 17 | mainEntry.filePath.endsWith(".plasmo.manifest.json") && 18 | mainEntry.meta?.webextEntry 19 | ) { 20 | return "manifest.json" 21 | } 22 | 23 | if (typeof mainEntry.meta?.bundlePath === "string") { 24 | return mainEntry.meta.bundlePath 25 | } 26 | 27 | return null 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /core/parcel-namer-manifest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/cli", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-optimizer-encapsulate/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-optimizer-encapsulate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-optimizer-encapsulate", 3 | "version": "0.0.8", 4 | "description": "Plasmo ECMAScript Encapsulation for Extension", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup --minify --clean", 12 | "dev": "tsup --sourcemap --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.8.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "@plasmo/utils": "workspace:*", 27 | "tsup": "8.4.0" 28 | }, 29 | "dependencies": { 30 | "@parcel/core": "2.9.3", 31 | "@parcel/plugin": "2.9.3", 32 | "@parcel/source-map": "2.1.1", 33 | "@parcel/types": "2.9.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/parcel-optimizer-encapsulate/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | * 5 | */ 6 | 7 | import { Optimizer } from "@parcel/plugin" 8 | import SourceMap from "@parcel/source-map" 9 | import type { PluginOptions } from "@parcel/types" 10 | 11 | import { vLog } from "@plasmo/utils/logging" 12 | 13 | const encapsulateGlobal = (name: string) => 14 | `var __${name}; typeof ${name} === "function" && (__${name}=${name},${name}=null);` 15 | 16 | const problematicGlobals = ["define"] 17 | 18 | const beforeContent = `(function(${problematicGlobals.join( 19 | "," 20 | )}){${problematicGlobals.map(encapsulateGlobal).join("")}` 21 | 22 | const afterContent = ` ${problematicGlobals 23 | .map((n) => `globalThis.${n}=__${n};`) 24 | .join("")} })(${problematicGlobals 25 | .map((n) => `globalThis.${n}`) 26 | .join(",")});` 27 | 28 | function getSourceMap(options: PluginOptions, map: SourceMap) { 29 | if (process.env.__PLASMO_FRAMEWORK_INTERNAL_SOURCE_MAPS !== "none") { 30 | const newMap = new SourceMap(options.projectRoot) 31 | const mapBuffer = map.toBuffer() 32 | const lineOffset = 1 33 | newMap.addBuffer(mapBuffer, lineOffset) 34 | return newMap 35 | } else { 36 | return null 37 | } 38 | } 39 | 40 | export default new Optimizer({ 41 | async optimize({ bundle, contents, map, options }) { 42 | vLog( 43 | "@plasmohq/parcel-optimizer-encapsulate", 44 | bundle.name, 45 | bundle.displayName 46 | ) 47 | 48 | return { 49 | contents: `${beforeContent}\n${contents}${afterContent}`, 50 | map: getSourceMap(options, map) 51 | } 52 | } 53 | }) 54 | -------------------------------------------------------------------------------- /core/parcel-optimizer-encapsulate/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/framework", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-optimizer-encapsulate/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup" 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"] 5 | }) 6 | -------------------------------------------------------------------------------- /core/parcel-optimizer-es/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-optimizer-es/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-optimizer-es", 3 | "version": "0.4.2", 4 | "description": "Plasmo ECMAScript Optimizer for Extension", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup --minify --clean", 12 | "dev": "tsup --sourcemap --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.8.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "@plasmo/utils": "workspace:*", 27 | "tsup": "8.4.0" 28 | }, 29 | "dependencies": { 30 | "@parcel/core": "2.9.3", 31 | "@parcel/plugin": "2.9.3", 32 | "@parcel/source-map": "2.1.1", 33 | "@parcel/utils": "2.9.3", 34 | "@swc/core": "1.11.13", 35 | "nullthrows": "1.1.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/parcel-optimizer-es/src/blob-to-string.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from "buffer" 2 | import { Readable } from "stream" 3 | 4 | type Blob = Buffer | Readable | string 5 | 6 | export function bufferStream(stream: Readable): Promise { 7 | return new Promise((resolve, reject) => { 8 | let buf = Buffer.from([]) 9 | stream.on("data", (data) => { 10 | buf = Buffer.concat([buf, data]) 11 | }) 12 | stream.on("end", () => { 13 | resolve(buf) 14 | }) 15 | stream.on("error", reject) 16 | }) 17 | } 18 | 19 | export async function blobToString(blob: Blob): Promise { 20 | if (typeof blob === "string") { 21 | return blob 22 | } else if (blob instanceof Readable) { 23 | return (await bufferStream(blob)).toString() 24 | } else if (blob instanceof Buffer) { 25 | return blob.toString() 26 | } else { 27 | return blob 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/parcel-optimizer-es/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/framework", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-optimizer-es/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup" 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"] 5 | }) 6 | -------------------------------------------------------------------------------- /core/parcel-packager/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-packager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-packager", 3 | "version": "0.6.15", 4 | "description": "Plasmo Parcel Packager for Web Extension Manifest", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup src/index.ts --minify --clean", 12 | "dev": "tsup src/index.ts --sourcemap --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.7.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "@plasmo/constants": "workspace:*", 27 | "@plasmo/utils": "workspace:*", 28 | "tsup": "8.4.0" 29 | }, 30 | "dependencies": { 31 | "@parcel/core": "2.9.3", 32 | "@parcel/plugin": "2.9.3", 33 | "@parcel/types": "2.9.3", 34 | "@parcel/utils": "2.9.3", 35 | "nullthrows": "1.1.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/parcel-packager/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | * 5 | * Based on: https://github.com/parcel-bundler/parcel/tree/v2/packages/packagers/webextension 6 | * MIT License 7 | */ 8 | import assert from "assert" 9 | import { Packager } from "@parcel/plugin" 10 | import type { Asset } from "@parcel/types" 11 | import { replaceURLReferences } from "@parcel/utils" 12 | 13 | import type { ExtensionManifest } from "@plasmo/constants" 14 | import { vLog } from "@plasmo/utils/logging" 15 | 16 | import { 17 | appendMv2Wars, 18 | appendMv3Wars, 19 | getWarsFromContentScripts 20 | } from "./get-web-accessible-resources" 21 | 22 | export default new Packager({ 23 | async package({ bundle, bundleGraph, options }) { 24 | vLog("@plasmohq/parcel-packager") 25 | const assets: Asset[] = [] 26 | bundle.traverseAssets((asset) => { 27 | assets.push(asset) 28 | }) 29 | 30 | const manifestEntryAssets = assets.filter( 31 | (a) => a.meta.webextEntry === true 32 | ) 33 | assert( 34 | assets.length === 2 && manifestEntryAssets.length === 1, 35 | "Web extension bundles must contain exactly one manifest asset and one runtime asset" 36 | ) 37 | const [manifestAsset] = manifestEntryAssets 38 | 39 | const manifest: ExtensionManifest = JSON.parse( 40 | await manifestAsset.getCode() 41 | ) 42 | 43 | const dependencies = manifestAsset.getDependencies() 44 | 45 | const wars = getWarsFromContentScripts( 46 | bundle, 47 | bundleGraph, 48 | dependencies, 49 | manifest.content_scripts 50 | ) 51 | 52 | if (manifest.manifest_version === 2) { 53 | appendMv2Wars(manifest, wars, options) 54 | } else { 55 | appendMv3Wars(manifest, wars, options) 56 | } 57 | 58 | const { contents } = replaceURLReferences({ 59 | bundle, 60 | bundleGraph, 61 | contents: JSON.stringify(manifest) 62 | }) 63 | 64 | return { 65 | contents 66 | } 67 | } 68 | }) 69 | -------------------------------------------------------------------------------- /core/parcel-packager/src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { NamedBundle } from "@parcel/types" 2 | import { relativeBundlePath } from "@parcel/utils" 3 | 4 | export function getRelativePath(from: NamedBundle, to: NamedBundle): string { 5 | return relativeBundlePath(from, to, { 6 | leadingDotSlash: false 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /core/parcel-packager/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/framework", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-resolver-post/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-resolver-post/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-resolver-post", 3 | "version": "0.4.6", 4 | "description": "Plasmo Parcel Resolver Post-processing", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup src/index.ts --minify --clean", 12 | "dev": "tsup src/index.ts --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.7.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "@plasmo/utils": "workspace:*" 27 | }, 28 | "dependencies": { 29 | "@parcel/core": "2.9.3", 30 | "@parcel/hash": "2.9.3", 31 | "@parcel/plugin": "2.9.3", 32 | "@parcel/types": "2.9.3", 33 | "@parcel/utils": "2.9.3", 34 | "tsup": "8.4.0", 35 | "typescript": "5.8.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/parcel-resolver-post/src/handle-hacks.ts: -------------------------------------------------------------------------------- 1 | // These are hack resolvers to get over some module resolution issues, with a PR to fix them upstream. 2 | 3 | import type { ResolverProps, ResolverResult } from "./shared" 4 | 5 | // Last resort resolver for weird packages: 6 | export async function handleHacks({ 7 | specifier, 8 | dependency 9 | }: ResolverProps): Promise { 10 | switch (specifier) { 11 | // Example of a resolved hack: 12 | // TODO: remove this when we have other hacks here... 13 | // case "svelte/internal/disclose-version": { 14 | // // https://github.com/sveltejs/svelte/pull/8874 15 | // const sveltePjPath = require.resolve("svelte/package.json", { 16 | // paths: [dependency.resolveFrom] 17 | // }) 18 | 19 | // return { 20 | // filePath: join( 21 | // dirname(sveltePjPath), 22 | // "src", 23 | // "runtime", 24 | // "internal", 25 | // "disclose-version", 26 | // "index.js" 27 | // ) 28 | // } 29 | // } 30 | 31 | default: 32 | return null 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/parcel-resolver-post/src/handle-module-exports.ts: -------------------------------------------------------------------------------- 1 | import type { ResolverProps, ResolverResult } from "./shared" 2 | 3 | // Last resort resolver for weird packages: 4 | export async function handleModuleExport({ 5 | specifier, 6 | dependency 7 | }: ResolverProps): Promise { 8 | // Ignore relative path 9 | if (specifier.startsWith("./") || specifier.startsWith("../")) { 10 | return null 11 | } 12 | 13 | try { 14 | const filePath = require.resolve(specifier, { 15 | paths: [dependency.resolveFrom] 16 | }) 17 | 18 | return { 19 | filePath 20 | } 21 | } catch {} 22 | 23 | return null 24 | } 25 | -------------------------------------------------------------------------------- /core/parcel-resolver-post/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Resolver } from "@parcel/plugin" 2 | 3 | import { handleHacks } from "./handle-hacks" 4 | import { handleModuleExport } from "./handle-module-exports" 5 | import { handleTsPath } from "./handle-ts-path" 6 | 7 | export default new Resolver({ 8 | async resolve(props) { 9 | return ( 10 | (await handleHacks(props)) || 11 | (await handleTsPath(props)) || 12 | (await handleModuleExport(props)) || 13 | null 14 | ) 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /core/parcel-resolver-post/src/shared.ts: -------------------------------------------------------------------------------- 1 | import type { Resolver } from "@parcel/plugin" 2 | import type { ResolveResult } from "@parcel/types" 3 | 4 | export const relevantExtensionList = [ 5 | ".ts", 6 | ".tsx", 7 | ".svelte", 8 | ".vue", 9 | ".json", 10 | 11 | ".js", 12 | ".jsx" 13 | ] as const 14 | 15 | export const relevantExtensionSet = new Set(relevantExtensionList) 16 | 17 | type ResolveFx = ConstructorParameters[0]["resolve"] 18 | 19 | export type ResolverResult = ResolveResult 20 | export type ResolverProps = Parameters[0] 21 | -------------------------------------------------------------------------------- /core/parcel-resolver-post/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { statSync } from "fs" 2 | import { resolve } from "path" 3 | import type { ResolveResult } from "@parcel/types" 4 | 5 | import { relevantExtensionList, type ResolverResult } from "./shared" 6 | 7 | const WEBPACK_IMPORT_REGEX = /\S+-loader\S*!\S+/g 8 | 9 | export function checkWebpackSpecificImportSyntax(specifier = "") { 10 | // Throw user friendly errors on special webpack loader syntax 11 | // ex. `imports-loader?$=jquery!./example.js` 12 | if (WEBPACK_IMPORT_REGEX.test(specifier)) { 13 | throw new Error( 14 | `The import path: ${specifier} is using webpack specific loader import syntax, which isn't supported by Parcel.` 15 | ) 16 | } 17 | } 18 | 19 | export function trimStar(str: string) { 20 | return trim(str, "*") 21 | } 22 | 23 | export function trim(str: string, trim: string) { 24 | if (str.endsWith(trim)) { 25 | str = str.substring(0, str.length - trim.length) 26 | } 27 | return str 28 | } 29 | 30 | const isFile = (filePath: string) => { 31 | try { 32 | return statSync(filePath).isFile() 33 | } catch { 34 | return false 35 | } 36 | } 37 | 38 | export function findModule( 39 | absoluteBaseFile: string, 40 | checkingExts = relevantExtensionList as readonly string[] 41 | ) { 42 | return checkingExts 43 | .flatMap((ext) => [ 44 | resolve(`${absoluteBaseFile}${ext}`), 45 | resolve(absoluteBaseFile, `index${ext}`) 46 | ]) 47 | .find(isFile) 48 | } 49 | 50 | /** 51 | * Look for source code file (crawl index) 52 | */ 53 | export const resolveSourceIndex = async ( 54 | absoluteBaseFile: string, 55 | checkingExts = relevantExtensionList as readonly string[], 56 | opts = {} as Partial 57 | ): Promise => { 58 | const filePath = findModule(absoluteBaseFile, checkingExts) 59 | 60 | if (!filePath) { 61 | return null 62 | } 63 | 64 | return { filePath, ...opts } 65 | } 66 | -------------------------------------------------------------------------------- /core/parcel-resolver-post/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/cli", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-resolver/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-resolver/index.mjs: -------------------------------------------------------------------------------- 1 | import { build } from "esbuild" 2 | import glob from "fast-glob" 3 | 4 | const commonConfig = { 5 | bundle: true, 6 | minify: true, 7 | 8 | platform: "browser", 9 | format: "cjs", 10 | target: ["chrome74", "safari11"], 11 | outdir: "dist/polyfills" 12 | } 13 | 14 | async function buildProdPolyfills() { 15 | const prodPolyfills = await glob("./src/polyfills/**/*.ts", { 16 | onlyFiles: true 17 | }) 18 | 19 | await build({ 20 | ...commonConfig, 21 | entryPoints: prodPolyfills, 22 | alias: { 23 | stream: "stream-browserify", 24 | http: "stream-http" 25 | } 26 | }) 27 | } 28 | 29 | async function buildDevPolyfills() { 30 | const devPolyfills = await glob("./src/dev-polyfills/**/*.ts", { 31 | onlyFiles: true 32 | }) 33 | 34 | await build({ 35 | ...commonConfig, 36 | entryPoints: devPolyfills, 37 | define: { 38 | "process.env.NODE_ENV": "'development'" 39 | } 40 | }) 41 | } 42 | 43 | async function main() { 44 | await Promise.all([buildProdPolyfills(), buildDevPolyfills()]) 45 | } 46 | 47 | main() 48 | -------------------------------------------------------------------------------- /core/parcel-resolver/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-resolver", 3 | "version": "0.14.2", 4 | "description": "Plasmo Parcel Resolver", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "dev": "run-s build:polyfills dev:resolver", 11 | "dev:resolver": "tsup src/index.ts --watch", 12 | "build": "run-s build:*", 13 | "build:resolver": "tsup src/index.ts --minify --clean", 14 | "build:polyfills": "node index.mjs", 15 | "prepublishOnly": "pnpm build" 16 | }, 17 | "author": "Plasmo Corp. ", 18 | "homepage": "https://docs.plasmo.com/", 19 | "engines": { 20 | "parcel": ">= 2.7.0" 21 | }, 22 | "license": "MIT", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/PlasmoHQ/plasmo.git" 26 | }, 27 | "devDependencies": { 28 | "@plasmo/config": "workspace:*", 29 | "@plasmo/utils": "workspace:*", 30 | "@plasmohq/rps": "workspace:*", 31 | "assert": "2.1.0", 32 | "browserify-zlib": "0.2.0", 33 | "buffer": "6.0.3", 34 | "console-browserify": "1.2.0", 35 | "constants-browserify": "1.0.0", 36 | "crc-32": "1.2.2", 37 | "crypto-browserify": "3.12.1", 38 | "domain-browser": "5.7.0", 39 | "esbuild": "0.25.1", 40 | "events": "3.3.0", 41 | "https-browserify": "1.0.0", 42 | "os-browserify": "0.3.0", 43 | "path-browserify": "1.0.1", 44 | "process": "0.11.10", 45 | "punycode": "2.3.1", 46 | "querystring-es3": "0.2.1", 47 | "react-refresh": "0.16.0", 48 | "stream-browserify": "3.0.0", 49 | "stream-http": "3.2.0", 50 | "string_decoder": "1.3.0", 51 | "timers-browserify": "2.0.12", 52 | "tsup": "8.4.0", 53 | "tty-browserify": "0.0.1", 54 | "url": "0.11.4", 55 | "util": "0.12.5", 56 | "vm-browserify": "1.1.2" 57 | }, 58 | "dependencies": { 59 | "@parcel/core": "2.9.3", 60 | "@parcel/hash": "2.9.3", 61 | "@parcel/plugin": "2.9.3", 62 | "@parcel/types": "2.9.3", 63 | "fast-glob": "3.3.3", 64 | "fs-extra": "11.3.0", 65 | "got": "14.4.6" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/dev-polyfills/react-refresh.ts: -------------------------------------------------------------------------------- 1 | import refresh from "react-refresh" 2 | 3 | export * from "react-refresh" 4 | export default refresh 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/dev-polyfills/react-refresh/runtime.ts: -------------------------------------------------------------------------------- 1 | import refreshRuntime from "react-refresh/runtime" 2 | 3 | export * from "react-refresh/runtime" 4 | export default refreshRuntime 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/handle-absolute-root.ts: -------------------------------------------------------------------------------- 1 | import { extname, isAbsolute, join, resolve } from "path" 2 | import { pathExists, pathExistsSync } from "fs-extra" 3 | 4 | import { 5 | relevantExtensionList, 6 | resolveSourceIndex, 7 | type ResolverProps, 8 | type ResolverResult 9 | } from "./shared" 10 | 11 | export async function handleAbsoluteRoot({ 12 | specifier, 13 | dependency 14 | }: ResolverProps): Promise { 15 | if (specifier[0] !== "/") { 16 | return null 17 | } 18 | 19 | if (pathExistsSync(specifier)) { 20 | return { 21 | filePath: specifier 22 | } 23 | } 24 | 25 | const absoluteBaseFile = resolve( 26 | join(process.env.PLASMO_PROJECT_DIR, specifier.slice(1)) 27 | ) 28 | 29 | const importExt = extname(absoluteBaseFile) 30 | 31 | if (importExt.length > 0 && pathExistsSync(absoluteBaseFile)) { 32 | return { 33 | filePath: absoluteBaseFile 34 | } 35 | } 36 | 37 | const parentExt = extname(dependency.resolveFrom) 38 | 39 | const checkingExts = [ 40 | parentExt, 41 | ...relevantExtensionList.filter((ext) => ext !== parentExt) 42 | ] 43 | 44 | return resolveSourceIndex(absoluteBaseFile, checkingExts) 45 | } 46 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/handle-alias.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path" 2 | 3 | import { isReadable } from "@plasmo/utils/fs" 4 | 5 | import { state, type ResolverProps, type ResolverResult } from "./shared" 6 | 7 | export async function handleAlias({ 8 | specifier 9 | }: ResolverProps): Promise { 10 | if (!state.aliasMap.has(specifier)) { 11 | return null 12 | } 13 | 14 | const absPath = resolve(state.aliasMap.get(specifier)) 15 | 16 | const hasLocalAlias = await isReadable(absPath) 17 | 18 | if (!hasLocalAlias) { 19 | return null 20 | } 21 | 22 | return { 23 | filePath: absPath, 24 | priority: "sync" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/handle-plasmo-internal.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path" 2 | 3 | import { 4 | resolveSourceIndex, 5 | state, 6 | type ResolverProps, 7 | type ResolverResult 8 | } from "./shared" 9 | 10 | const resolveByPrefix = (specifier = "", prefix = "", prefixPath = "") => { 11 | if (!specifier.startsWith(prefix)) { 12 | return null 13 | } 14 | 15 | const [_, specifierPath] = specifier.split(prefix) 16 | 17 | return resolveSourceIndex(resolve(prefixPath, specifierPath)) 18 | } 19 | 20 | export async function handlePlasmoInternal({ 21 | specifier 22 | }: ResolverProps): Promise { 23 | return resolveByPrefix( 24 | specifier, 25 | "@plasmo-static-common/", 26 | resolve(state.dotPlasmoDirectory, "static", "common") 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/handle-polyfill.ts: -------------------------------------------------------------------------------- 1 | import { state, type ResolverProps, type ResolverResult } from "./shared" 2 | 3 | export async function handlePolyfill({ 4 | specifier 5 | }: ResolverProps): Promise { 6 | if (!state.polyfillMap.has(specifier)) { 7 | return null 8 | } 9 | 10 | return { 11 | filePath: state.polyfillMap.get(specifier), 12 | priority: "sync" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/handle-remote-caching.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path" 2 | import { hashString } from "@parcel/hash" 3 | 4 | import { injectEnv } from "@plasmo/utils/env" 5 | import { vLog } from "@plasmo/utils/logging" 6 | 7 | import { 8 | relevantExtensionList, 9 | state, 10 | type ResolverProps, 11 | type ResolverResult 12 | } from "./shared" 13 | 14 | const cookCode = async (target: URL, code: string) => { 15 | if (target.origin === "https://www.googletagmanager.com") { 16 | return code.replace(/http:/g, "chrome-extension:") 17 | } else { 18 | return code 19 | } 20 | } 21 | 22 | // TODO: Some kind of caching mechanism would be nice 23 | export async function handleRemoteCaching({ 24 | specifier, 25 | dependency, 26 | options 27 | }: ResolverProps): Promise { 28 | if (!specifier.startsWith("https://")) { 29 | return null 30 | } 31 | 32 | // Only these extensions parents are allowed to cache remote dependencies 33 | if ( 34 | !relevantExtensionList.some((ext) => dependency.sourcePath.endsWith(ext)) 35 | ) { 36 | return null 37 | } 38 | const target = new URL(injectEnv(specifier)) 39 | 40 | const fileType = target.searchParams.get("plasmo-ext") || "js" 41 | 42 | try { 43 | const filePath = resolve( 44 | state.remoteCacheDirectory, 45 | `${hashString(specifier)}.${fileType}` 46 | ) 47 | const code = (await state.got(target.toString()).text()) as string 48 | 49 | const cookedCode = await cookCode(target, code) 50 | 51 | await options.inputFS.rimraf(filePath) 52 | 53 | await options.inputFS.writeFile(filePath, cookedCode, { 54 | mode: 0o664 55 | }) 56 | 57 | vLog(`Caching HTTPS dependency: ${specifier}`) 58 | 59 | return { 60 | priority: "lazy", 61 | sideEffects: true, 62 | filePath 63 | } 64 | } catch (error) { 65 | return { 66 | diagnostics: [ 67 | { 68 | message: `Cannot download the resource from ${specifier}`, 69 | hints: [error.message] 70 | } 71 | ] 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/handle-tilde-src.ts: -------------------------------------------------------------------------------- 1 | import { extname, join, resolve } from "path" 2 | 3 | import { 4 | customPipelineSet, 5 | relevantExtensionList, 6 | relevantExtensionSet, 7 | resolveSourceIndex, 8 | type ResolverProps, 9 | type ResolverResult 10 | } from "./shared" 11 | 12 | export async function handleTildeSrc({ 13 | pipeline, 14 | specifier, 15 | dependency 16 | }: ResolverProps): Promise { 17 | if (specifier[0] !== "~") { 18 | return null 19 | } 20 | 21 | const absoluteBaseFile = resolve( 22 | join(process.env.PLASMO_SRC_DIR, specifier.slice(1)) 23 | ) 24 | 25 | if (customPipelineSet.has(pipeline)) { 26 | return { 27 | filePath: absoluteBaseFile 28 | } 29 | } 30 | 31 | const importExt = extname(absoluteBaseFile) 32 | 33 | // TODO: Potentially resolve other type of files (less import etc...) that Parcel doesn't account for 34 | if (importExt.length > 0 && relevantExtensionSet.has(importExt as any)) { 35 | return { 36 | filePath: absoluteBaseFile 37 | } 38 | } 39 | 40 | const parentExt = extname(dependency.resolveFrom) 41 | 42 | // console.log(`tildeSrc: resolveFrom: ${dependency.resolveFrom}`) 43 | 44 | const checkingExts = [ 45 | parentExt, 46 | ...relevantExtensionList.filter((ext) => ext !== parentExt) 47 | ] 48 | 49 | // console.log(`tildeSrc: ${potentialFiles}`) 50 | 51 | return resolveSourceIndex(absoluteBaseFile, checkingExts) 52 | } 53 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Resolver } from "@parcel/plugin" 2 | 3 | import { handleAbsoluteRoot } from "./handle-absolute-root" 4 | import { handleAlias } from "./handle-alias" 5 | import { handlePlasmoInternal } from "./handle-plasmo-internal" 6 | import { handlePolyfill } from "./handle-polyfill" 7 | import { handleRemoteCaching } from "./handle-remote-caching" 8 | import { handleTildeSrc } from "./handle-tilde-src" 9 | import { initializeState } from "./shared" 10 | 11 | export default new Resolver({ 12 | async resolve(props) { 13 | await initializeState(props) 14 | 15 | return ( 16 | (await handleAlias(props)) || 17 | (await handlePlasmoInternal(props)) || 18 | (await handlePolyfill(props)) || 19 | (await handleRemoteCaching(props)) || 20 | (await handleTildeSrc(props)) || 21 | (await handleAbsoluteRoot(props)) || 22 | null 23 | ) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/assert.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert/build/assert" 2 | 3 | export * from "assert/build/assert" 4 | export default assert 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/buffer.ts: -------------------------------------------------------------------------------- 1 | import buffer from "buffer/index" 2 | 3 | export * from "buffer/index" 4 | export default buffer 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/console.ts: -------------------------------------------------------------------------------- 1 | import console from "console-browserify" 2 | 3 | export * from "console-browserify" 4 | export default console 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/constants.ts: -------------------------------------------------------------------------------- 1 | import constants from "constants-browserify/constants.json" 2 | 3 | export default constants 4 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/crc-32.ts: -------------------------------------------------------------------------------- 1 | import crc32 from "crc-32" 2 | 3 | globalThis.DO_NOT_EXPORT_CRC = true 4 | 5 | export * from "crc-32" 6 | export default crc32 7 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/crc-32/crc32c.ts: -------------------------------------------------------------------------------- 1 | import crc32c from "crc-32/crc32c" 2 | 3 | globalThis.DO_NOT_EXPORT_CRC = true 4 | 5 | export * from "crc-32/crc32c" 6 | export default crc32c 7 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/crypto.ts: -------------------------------------------------------------------------------- 1 | import crypto from "crypto-browserify" 2 | 3 | export * from "crypto-browserify" 4 | export default crypto 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/domain.ts: -------------------------------------------------------------------------------- 1 | import domain from "domain-browser" 2 | 3 | export * from "domain-browser" 4 | export default domain 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/events.ts: -------------------------------------------------------------------------------- 1 | import events from "events/events" 2 | 3 | export * from "events/events" 4 | export default events 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/http.ts: -------------------------------------------------------------------------------- 1 | import http from "stream-http" 2 | 3 | export * from "stream-http" 4 | export default http 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/https.ts: -------------------------------------------------------------------------------- 1 | import https from "https-browserify" 2 | 3 | export * from "https-browserify" 4 | export default https 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/os.ts: -------------------------------------------------------------------------------- 1 | import os from "os-browserify/browser" 2 | 3 | export * from "os-browserify/browser" 4 | export default os 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/path.ts: -------------------------------------------------------------------------------- 1 | import path from "path-browserify" 2 | 3 | export * from "path-browserify" 4 | export default path 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/process.ts: -------------------------------------------------------------------------------- 1 | import process from "process/browser" 2 | 3 | export * from "process/browser" 4 | export default process 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/punycode.ts: -------------------------------------------------------------------------------- 1 | import punycode from "punycode/punycode.es6" 2 | 3 | export * from "punycode/punycode.es6" 4 | export default punycode 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/querystring.ts: -------------------------------------------------------------------------------- 1 | import querystring from "querystring-es3" 2 | 3 | export * from "querystring-es3" 4 | export default querystring 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/stream.ts: -------------------------------------------------------------------------------- 1 | import stream from "stream-browserify" 2 | 3 | export * from "stream-browserify" 4 | export default stream 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/string_decoder.ts: -------------------------------------------------------------------------------- 1 | import stringDecoder from "string_decoder/lib/string_decoder" 2 | 3 | export * from "string_decoder/lib/string_decoder" 4 | export default stringDecoder 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/sys.ts: -------------------------------------------------------------------------------- 1 | import sys from "util/util" 2 | 3 | export * from "util/util" 4 | export default sys 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/timers.ts: -------------------------------------------------------------------------------- 1 | import timers from "timers-browserify" 2 | 3 | export * from "timers-browserify" 4 | export default timers 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/tty.ts: -------------------------------------------------------------------------------- 1 | import tty from "tty-browserify" 2 | 3 | export * from "tty-browserify" 4 | export default tty 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/url.ts: -------------------------------------------------------------------------------- 1 | import url from "url/url" 2 | 3 | export * from "url/url" 4 | export default url 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/util.ts: -------------------------------------------------------------------------------- 1 | import util from "util/util" 2 | 3 | export * from "util/util" 4 | export default util 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/vm.ts: -------------------------------------------------------------------------------- 1 | import vm from "vm-browserify" 2 | 3 | export * from "vm-browserify" 4 | export default vm 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/src/polyfills/zlib.ts: -------------------------------------------------------------------------------- 1 | import zlib from "browserify-zlib" 2 | 3 | export * from "browserify-zlib" 4 | export default zlib 5 | -------------------------------------------------------------------------------- /core/parcel-resolver/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/cli", 3 | "include": ["src/**/*.ts", "tsup.config.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-runtime/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-runtime/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-runtime", 3 | "version": "0.25.3", 4 | "description": "Plasmo Parcel Runtime", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup --minify --clean", 12 | "dev": "tsup --sourcemap --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.7.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "@plasmo/framework-shared": "workspace:*", 27 | "@plasmo/utils": "workspace:*", 28 | "@plasmohq/persistent": "workspace:*", 29 | "@types/chrome": "0.0.312", 30 | "tsup": "8.4.0" 31 | }, 32 | "dependencies": { 33 | "@types/trusted-types": "2.0.7", 34 | "@parcel/core": "2.9.3", 35 | "@parcel/plugin": "2.9.3", 36 | "react-refresh": "0.16.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/parcel-runtime/src/utils/0-patch-module.ts: -------------------------------------------------------------------------------- 1 | import { vLog } from "@plasmo/utils/logging" 2 | 3 | import type { BackgroundMessage, ExtensionApi, RuntimeData } from "../types" 4 | 5 | // @ts-ignore 6 | export const runtimeData = __plasmo_runtime_data__ as RuntimeData 7 | 8 | module.bundle.HMR_BUNDLE_ID = runtimeData.bundleId 9 | 10 | globalThis.process = { 11 | argv: [], 12 | env: { 13 | VERBOSE: runtimeData.verbose 14 | } 15 | } as any 16 | 17 | const OldModule = module.bundle.Module 18 | 19 | function Module(moduleName: string) { 20 | OldModule.call(this, moduleName) 21 | this.hot = { 22 | data: module.bundle.hotData[moduleName], 23 | _acceptCallbacks: [], 24 | _disposeCallbacks: [], 25 | accept: function (fn) { 26 | this._acceptCallbacks.push(fn || function () {}) 27 | }, 28 | dispose: function (fn) { 29 | this._disposeCallbacks.push(fn) 30 | } 31 | } 32 | module.bundle.hotData[moduleName] = undefined 33 | } 34 | 35 | module.bundle.Module = Module 36 | module.bundle.hotData = {} 37 | 38 | export const extCtx: ExtensionApi = 39 | globalThis.browser || globalThis.chrome || null 40 | 41 | export async function triggerReload(fullReload = false) { 42 | if (fullReload) { 43 | vLog("Triggering full reload") 44 | extCtx.runtime.sendMessage({ 45 | __plasmo_full_reload__: true 46 | }) 47 | } else { 48 | globalThis.location?.reload?.() 49 | } 50 | } 51 | 52 | export function getHostname() { 53 | if (!runtimeData.host || runtimeData.host === "0.0.0.0") { 54 | return location.protocol.indexOf("http") === 0 55 | ? location.hostname 56 | : "localhost" 57 | } 58 | 59 | return runtimeData.host 60 | } 61 | 62 | export function getSocketHostname() { 63 | if (!runtimeData.host || runtimeData.host === "0.0.0.0") { 64 | return "localhost"; 65 | } 66 | 67 | return runtimeData.host 68 | } 69 | 70 | export function getPort() { 71 | return runtimeData.port || location.port 72 | } 73 | 74 | export const PAGE_PORT_PREFIX = `__plasmo_runtime_page_` 75 | export const SCRIPT_PORT_PREFIX = `__plasmo_runtime_script_` 76 | -------------------------------------------------------------------------------- /core/parcel-runtime/src/utils/bgsw.ts: -------------------------------------------------------------------------------- 1 | import { extCtx, getHostname, getPort, runtimeData } from "./0-patch-module" 2 | 3 | declare const globalThis: ServiceWorkerGlobalScope 4 | 5 | const devServer = `${ 6 | runtimeData.secure ? "https" : "http" 7 | }://${getHostname()}:${getPort()}/` 8 | 9 | export async function pollingDevServer(delay = 1470) { 10 | while (true) { 11 | try { 12 | await fetch(devServer) 13 | break 14 | } catch (e) { 15 | await new Promise((resolve) => setTimeout(resolve, delay)) 16 | } 17 | } 18 | } 19 | 20 | if (extCtx.runtime.getManifest().manifest_version === 3) { 21 | const proxyLoc = extCtx.runtime.getURL("/__plasmo_hmr_proxy__?url=") 22 | 23 | globalThis.addEventListener("fetch", function (evt) { 24 | const reqUrl = evt.request.url 25 | if (reqUrl.startsWith(proxyLoc)) { 26 | const url = new URL(decodeURIComponent(reqUrl.slice(proxyLoc.length))) 27 | if ( 28 | url.hostname === runtimeData.host && 29 | url.port === `${runtimeData.port}` 30 | ) { 31 | url.searchParams.set("t", Date.now().toString()) 32 | evt.respondWith( 33 | fetch(url).then( 34 | (res) => 35 | new Response(res.body, { 36 | headers: { 37 | "Content-Type": 38 | res.headers.get("Content-Type") ?? "text/javascript" 39 | } 40 | }) 41 | ) 42 | ) 43 | } else { 44 | evt.respondWith( 45 | new Response("Plasmo HMR", { 46 | status: 200, 47 | statusText: "Testing" 48 | }) 49 | ) 50 | } 51 | } 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /core/parcel-runtime/src/utils/react-refresh.ts: -------------------------------------------------------------------------------- 1 | import refreshRuntime from "react-refresh/runtime" 2 | 3 | export async function injectReactRefresh() { 4 | refreshRuntime.injectIntoGlobalHook(window) 5 | 6 | window.$RefreshReg$ = function () {} 7 | window.$RefreshSig$ = function () { 8 | return function (type) { 9 | return type 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /core/parcel-runtime/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/framework", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"], 5 | "compilerOptions": { 6 | "lib": ["WebWorker", "dom"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /core/parcel-runtime/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup" 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts", "src/runtimes/*"] 5 | }) 6 | -------------------------------------------------------------------------------- /core/parcel-transformer-inject-env/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-transformer-inject-env/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-transformer-inject-env", 3 | "version": "0.2.12", 4 | "description": "Plasmo Parcel Transformer to inject environment variables", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup src/index.ts --minify --clean", 12 | "dev": "tsup src/index.ts --sourcemap --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.7.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "@plasmo/utils": "workspace:*", 27 | "tsup": "8.4.0" 28 | }, 29 | "dependencies": { 30 | "@parcel/core": "2.9.3", 31 | "@parcel/plugin": "2.9.3", 32 | "@parcel/types": "2.9.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/parcel-transformer-inject-env/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Transformer } from "@parcel/plugin" 2 | 3 | import { injectEnv } from "@plasmo/utils/env" 4 | 5 | export default new Transformer({ 6 | async transform({ asset, options }) { 7 | const code = await asset.getCode() 8 | 9 | const injectedCode = injectEnv(code, options.env) 10 | 11 | asset.setCode(injectedCode) 12 | 13 | return [asset] 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /core/parcel-transformer-inject-env/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/cli", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-transformer-inline-css/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-transformer-inline-css/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-transformer-inline-css", 3 | "version": "0.3.12", 4 | "description": "Plasmo Parcel Transformer for inline CSS", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup src/index.ts --minify --clean", 12 | "dev": "tsup src/index.ts --sourcemap --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.7.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "tsup": "8.4.0" 27 | }, 28 | "dependencies": { 29 | "@parcel/core": "2.9.3", 30 | "@parcel/plugin": "2.9.3", 31 | "@parcel/utils": "2.9.3", 32 | "browserslist": "4.24.4", 33 | "lightningcss": "1.21.8" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/parcel-transformer-inline-css/src/get-tagets.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | * 5 | * Based on: https://github.com/parcel-bundler/parcel/blob/7023c08b7e99a9b8fd3c04995e4ef7ca92dee5c1/packages/transformers/css/src/CSSTransformer.js 6 | * MIT License 7 | */ 8 | 9 | import browserslist from "browserslist" 10 | import { browserslistToTargets } from "lightningcss" 11 | 12 | let cache = new Map() 13 | 14 | export function getTargets(browsers) { 15 | if (browsers == null) { 16 | return undefined 17 | } 18 | 19 | let cached = cache.get(browsers) 20 | if (cached != null) { 21 | return cached 22 | } 23 | 24 | let targets = browserslistToTargets(browserslist(browsers)) 25 | 26 | cache.set(browsers, targets) 27 | return targets 28 | } 29 | -------------------------------------------------------------------------------- /core/parcel-transformer-inline-css/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2024 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | * 5 | * Based on: https://github.com/parcel-bundler/parcel/blob/7023c08b7e99a9b8fd3c04995e4ef7ca92dee5c1/packages/transformers/css/src/CSSTransformer.js 6 | * MIT License 7 | */ 8 | 9 | import { relative } from "path" 10 | import { Transformer } from "@parcel/plugin" 11 | import { remapSourceLocation } from "@parcel/utils" 12 | import { transform } from "lightningcss" 13 | 14 | import { getTargets } from "./get-tagets" 15 | 16 | export default new Transformer({ 17 | async transform({ asset, options }) { 18 | // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated. 19 | // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced. 20 | const [code, originalMap] = await Promise.all([ 21 | asset.getBuffer(), 22 | asset.getMap() 23 | ]) 24 | 25 | const targets = getTargets(asset.env.engines.browsers) 26 | 27 | const res = transform({ 28 | filename: relative(options.projectRoot, asset.filePath), 29 | targets, 30 | code, 31 | cssModules: true, 32 | analyzeDependencies: asset.meta.hasDependencies !== false, 33 | sourceMap: !!asset.env.sourceMap 34 | }) 35 | 36 | asset.setBuffer(Buffer.from(res.code)) 37 | 38 | if (res.dependencies) { 39 | for (let dep of res.dependencies) { 40 | const loc = !originalMap 41 | ? dep.loc 42 | : remapSourceLocation(dep.loc, originalMap) 43 | 44 | if (dep.type === "import" && !res.exports) { 45 | asset.addDependency({ 46 | specifier: dep.url, 47 | specifierType: "url", 48 | loc, 49 | meta: { 50 | // For the glob resolver to distinguish between `@import` and other URL dependencies. 51 | isCSSImport: true, 52 | media: dep.media 53 | } 54 | }) 55 | } else if (dep.type === "url") { 56 | asset.addURLDependency(dep.url, { 57 | loc, 58 | meta: { 59 | placeholder: dep.placeholder 60 | } 61 | }) 62 | } 63 | } 64 | } 65 | 66 | return [asset] 67 | } 68 | }) 69 | -------------------------------------------------------------------------------- /core/parcel-transformer-inline-css/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/cli", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-transformer-lab/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-transformer-lab/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-transformer-lab", 3 | "version": "0.1.12", 4 | "description": "Plasmo Parcel Transformer Laboratory - a way to experiment with how the transformer works", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup src/index.ts --minify --clean", 12 | "dev": "tsup src/index.ts --sourcemap --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.7.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "@plasmo/utils": "workspace:*", 27 | "tsup": "8.4.0" 28 | }, 29 | "dependencies": { 30 | "@parcel/core": "2.9.3", 31 | "@parcel/diagnostic": "2.9.3", 32 | "@parcel/fs": "2.9.3", 33 | "@parcel/plugin": "2.9.3", 34 | "@parcel/types": "2.9.3", 35 | "@parcel/utils": "2.9.3" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/parcel-transformer-lab/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | */ 5 | import { Transformer } from "@parcel/plugin" 6 | 7 | import { iLog, vLog } from "@plasmo/utils/logging" 8 | 9 | import { initState } from "./state" 10 | 11 | async function collectDependencies() {} 12 | 13 | export default new Transformer({ 14 | async transform({ asset, options }) { 15 | vLog("@plasmohq/parcel-transformer-lab") 16 | const code = await asset.getCode() 17 | 18 | const { state, getAssets } = initState(asset, code, options.hmrOptions) 19 | 20 | if (asset.filePath.includes("hook.ts")) { 21 | iLog("Hook file: ", asset.filePath) 22 | } 23 | 24 | if (asset.filePath.endsWith("options.tsx")) { 25 | iLog("MONITORING: ", asset.filePath) 26 | 27 | // asset.addDependency({ 28 | // specifier: "storage-hook", 29 | // specifierType: "esm", 30 | // bundleBehavior: "isolated", 31 | // resolveFrom: asset.filePath 32 | // }) 33 | } 34 | 35 | await collectDependencies() 36 | 37 | return getAssets() 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /core/parcel-transformer-lab/src/state.ts: -------------------------------------------------------------------------------- 1 | import type { FileSystem } from "@parcel/fs" 2 | import type { 3 | DependencyOptions, 4 | HMROptions, 5 | MutableAsset, 6 | TransformerResult 7 | } from "@parcel/types" 8 | 9 | export const state = { 10 | code: "", 11 | hot: false, 12 | fs: null as FileSystem, 13 | 14 | filePath: "", 15 | 16 | asset: null as MutableAsset, 17 | 18 | extraAssets: [] as TransformerResult[] 19 | } 20 | 21 | export const addExtraAssets = async ( 22 | filePath: string, 23 | bundlePath: string, 24 | type = "json", 25 | dependencies = [] as DependencyOptions[] 26 | ) => { 27 | state.extraAssets.push({ 28 | type, 29 | uniqueKey: bundlePath, 30 | content: await state.asset.fs.readFile(filePath, "utf8"), 31 | pipeline: type === "json" ? "raw-env" : undefined, 32 | bundleBehavior: "isolated", 33 | isBundleSplittable: type !== "json", 34 | env: state.asset.env, 35 | dependencies, 36 | meta: { 37 | bundlePath, 38 | webextEntry: false 39 | } 40 | }) 41 | } 42 | 43 | export const initState = ( 44 | asset: MutableAsset, 45 | code: string, 46 | hmrOptions: HMROptions | null | undefined 47 | ) => { 48 | state.code = code 49 | state.fs = asset.fs 50 | state.filePath = asset.filePath 51 | 52 | state.asset = asset 53 | 54 | return { 55 | state, 56 | getAssets: () => [...state.extraAssets, asset] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/parcel-transformer-lab/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/cli", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-transformer-manifest", 3 | "version": "0.21.1", 4 | "description": "Plasmo Parcel Transformer for Web Extension Manifest", 5 | "files": [ 6 | "dist", 7 | "runtime" 8 | ], 9 | "main": "dist/index.js", 10 | "scripts": { 11 | "prepublishOnly": "pnpm build", 12 | "build": "tsup src/index.ts --minify --clean", 13 | "dev": "tsup src/index.ts --sourcemap --watch" 14 | }, 15 | "author": "Plasmo Corp. ", 16 | "homepage": "https://docs.plasmo.com/", 17 | "engines": { 18 | "parcel": ">= 2.7.0" 19 | }, 20 | "license": "MIT", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/PlasmoHQ/plasmo.git" 24 | }, 25 | "devDependencies": { 26 | "@plasmo/config": "workspace:*", 27 | "@plasmo/utils": "workspace:*", 28 | "tsup": "8.4.0" 29 | }, 30 | "dependencies": { 31 | "@mischnic/json-sourcemap": "0.1.1", 32 | "@parcel/core": "2.9.3", 33 | "@parcel/diagnostic": "2.9.3", 34 | "@parcel/fs": "2.9.3", 35 | "@parcel/plugin": "2.9.3", 36 | "@parcel/types": "2.9.3", 37 | "@parcel/utils": "2.9.3", 38 | "content-security-policy-parser": "0.6.0", 39 | "json-schema-to-ts": "3.1.1", 40 | "nullthrows": "1.1.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/runtime/plasmo-default-background.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlasmoHQ/plasmo/9369e2835de237caa60620b74b12ea3e2d4f3600/core/parcel-transformer-manifest/runtime/plasmo-default-background.ts -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/src/csp-patch-hmr.ts: -------------------------------------------------------------------------------- 1 | import parseCSP from "content-security-policy-parser" 2 | 3 | const DEFAULT_INSERT = "'unsafe-eval'" 4 | 5 | export function cspPatchHMR( 6 | policy: string | null | undefined, 7 | insert = DEFAULT_INSERT 8 | ) { 9 | const defaultSrc = 10 | insert === DEFAULT_INSERT ? "'self' blob: filesystem:" : "'self'" 11 | 12 | if (policy) { 13 | const csp = parseCSP(policy) 14 | policy = "" 15 | 16 | if (!csp["script-src"]) { 17 | csp["script-src"] = [defaultSrc] 18 | } 19 | 20 | if (!csp["script-src"].includes(insert)) { 21 | csp["script-src"].push(insert) 22 | } 23 | 24 | if (csp.sandbox && !csp.sandbox.includes("allow-scripts")) { 25 | csp.sandbox.push("allow-scripts") 26 | } 27 | 28 | for (const k in csp) { 29 | policy += `${k} ${csp[k].join(" ")};` 30 | } 31 | 32 | return policy 33 | } else { 34 | return `script-src ${defaultSrc} ${insert};` + `object-src ${defaultSrc};` 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/src/handle-action.ts: -------------------------------------------------------------------------------- 1 | import { getJSONSourceLocation } from "@parcel/diagnostic" 2 | 3 | import { checkMV2, getState } from "./state" 4 | 5 | export async function handleAction() { 6 | const { program, filePath, ptrs, asset } = getState() 7 | const isMV2 = checkMV2(program) 8 | 9 | const browserActionName = isMV2 ? "browser_action" : "action" 10 | 11 | const browserAction = isMV2 ? program.browser_action : program.action 12 | 13 | if (!browserAction) { 14 | return 15 | } 16 | 17 | if (browserAction.theme_icons) { 18 | browserAction.theme_icons = browserAction.theme_icons.map( 19 | (themeIcon, themeIndex) => { 20 | for (const k of ["light", "dark"]) { 21 | const loc = getJSONSourceLocation( 22 | ptrs[`/${browserActionName}/theme_icons/${themeIndex}/${k}`], 23 | "value" 24 | ) 25 | 26 | themeIcon[k] = asset.addURLDependency(themeIcon[k], { 27 | loc: { 28 | ...loc, 29 | filePath 30 | } 31 | }) 32 | } 33 | 34 | return themeIcon 35 | } 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/src/handle-content-scripts.ts: -------------------------------------------------------------------------------- 1 | import { getJSONSourceLocation } from "@parcel/diagnostic" 2 | 3 | import { getState } from "./state" 4 | 5 | export function handleContentScripts() { 6 | const { program, asset, filePath, ptrs } = getState() 7 | if (!program.content_scripts) { 8 | return 9 | } 10 | 11 | for (let i = 0; i < program.content_scripts.length; ++i) { 12 | const contentScript = program.content_scripts[i] 13 | 14 | for (const k of ["css", "js"]) { 15 | const assets = contentScript[k] || [] 16 | 17 | for (let j = 0; j < assets.length; ++j) { 18 | assets[j] = asset.addURLDependency(assets[j], { 19 | bundleBehavior: "isolated", 20 | loc: { 21 | filePath, 22 | ...getJSONSourceLocation( 23 | ptrs[`/content_scripts/${i}/${k}/${j}`], 24 | "value" 25 | ) 26 | } 27 | }) 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/src/handle-declarative-net-request.ts: -------------------------------------------------------------------------------- 1 | import { getJSONSourceLocation } from "@parcel/diagnostic" 2 | 3 | import { getState } from "./state" 4 | 5 | export async function handleDeclarativeNetRequest() { 6 | const { program, filePath, ptrs, asset } = getState() 7 | if (!program.declarative_net_request) { 8 | return 9 | } 10 | 11 | const rrs = program.declarative_net_request.rule_resources 12 | 13 | if (!rrs) { 14 | return 15 | } 16 | 17 | program.declarative_net_request.rule_resources = rrs.map((resources, i) => { 18 | resources.path = asset.addURLDependency(resources.path, { 19 | pipeline: "raw-env", 20 | loc: { 21 | filePath, 22 | ...getJSONSourceLocation( 23 | ptrs[`/declarative_net_request/rule_resources/${i}/path`], 24 | "value" 25 | ) 26 | } 27 | }) 28 | 29 | return resources 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/src/handle-dictionaries.ts: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import ThrowableDiagnostic, { getJSONSourceLocation } from "@parcel/diagnostic" 3 | 4 | import { getState } from "./state" 5 | 6 | export function handleDictionaries() { 7 | const { program, ptrs, filePath, asset } = getState() 8 | if (!program.dictionaries) { 9 | return 10 | } 11 | 12 | for (const dict in program.dictionaries) { 13 | const sourceLoc = getJSONSourceLocation( 14 | ptrs[`/dictionaries/${dict}`], 15 | "value" 16 | ) 17 | const loc = { 18 | filePath, 19 | ...sourceLoc 20 | } 21 | const dictFile = program.dictionaries[dict] 22 | 23 | if (path.extname(dictFile) !== ".dic") { 24 | throw new ThrowableDiagnostic({ 25 | diagnostic: [ 26 | { 27 | message: "Invalid Web Extension manifest", 28 | origin: "@plasmohq/parcel-transformer-manifest", 29 | codeFrames: [ 30 | { 31 | filePath, 32 | codeHighlights: [ 33 | { 34 | ...sourceLoc, 35 | message: "Dictionaries must be .dic files" 36 | } 37 | ] 38 | } 39 | ] 40 | } 41 | ] 42 | }) 43 | } 44 | 45 | program.dictionaries[dict] = asset.addURLDependency(dictFile, { 46 | needsStableName: true, 47 | loc 48 | }) 49 | asset.addURLDependency(dictFile.slice(0, -4) + ".aff", { 50 | needsStableName: true, 51 | loc 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/src/handle-locales.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path" 2 | 3 | import { vLog, wLog } from "@plasmo/utils/logging" 4 | 5 | import { getState } from "./state" 6 | import { addExtraAssets, wLogOnce } from "./utils" 7 | 8 | export async function handleLocales() { 9 | const { program, asset, assetsDir, projectDir } = getState() 10 | const localesDir = [ 11 | resolve(projectDir, "locales"), 12 | resolve(assetsDir, "locales"), 13 | resolve(assetsDir, "_locales") 14 | ].find((dir) => asset.fs.existsSync(dir)) 15 | 16 | if (!localesDir) { 17 | return 18 | } 19 | 20 | const localeEntries = await asset.fs.readdir(localesDir) 21 | 22 | if (localeEntries.length === 0) { 23 | vLog("No locale found, skipping") 24 | return 25 | } 26 | 27 | if (!program.default_locale) { 28 | program.default_locale = localeEntries[0] 29 | wLogOnce(`default_locale not set, fallback to ${localeEntries[0]}`) 30 | } 31 | 32 | const defaultLocaleMessageExists = await asset.fs.exists( 33 | resolve(localesDir, program.default_locale, "messages.json") 34 | ) 35 | 36 | if (!defaultLocaleMessageExists) { 37 | wLog("Default locale message.json not found, skipping locale!") 38 | delete program.default_locale 39 | return 40 | } 41 | 42 | await Promise.all( 43 | localeEntries.map(async (locale) => { 44 | vLog(`Adding locale ${locale}`) 45 | const localeFilePath = resolve(localesDir, locale, "messages.json") 46 | if (await asset.fs.exists(localeFilePath)) { 47 | const bundlePath = `_locales/${locale}/messages.json` 48 | asset.invalidateOnFileChange(localeFilePath) 49 | await addExtraAssets(localeFilePath, bundlePath) 50 | } 51 | }) 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/src/handle-tabs.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path" 2 | 3 | import { vLog } from "@plasmo/utils/logging" 4 | 5 | import { getState } from "./state" 6 | 7 | export async function handleTabs() { 8 | const { asset, dotPlasmoDir, srcDir } = getState() 9 | const srcTabsDir = resolve(srcDir, "tabs") 10 | const dotTabsDir = resolve(dotPlasmoDir, "tabs") 11 | 12 | const [dotTabsDirExists, srcTabsDirExists] = await Promise.all([ 13 | asset.fs.exists(dotTabsDir), 14 | asset.fs.exists(srcTabsDir) 15 | ]) 16 | 17 | if (!dotTabsDirExists || !srcTabsDirExists) { 18 | return 19 | } 20 | 21 | const tabsEntries = await asset.fs.readdir(srcTabsDir) 22 | 23 | if (tabsEntries.length === 0) { 24 | vLog(`No tab found in ${srcTabsDir}, skipping`) 25 | return 26 | } 27 | 28 | const entryNames = tabsEntries.map((entry) => { 29 | const token = entry.split(".") 30 | token.pop() 31 | return [entry, `${token.join(".")}.html`] 32 | }) 33 | 34 | await Promise.all( 35 | entryNames.map(async ([entry, htmlEntry]) => { 36 | const entryPath = resolve(dotTabsDir, htmlEntry) 37 | const srcEntryPath = resolve(srcTabsDir, entry) 38 | if ( 39 | (await asset.fs.exists(entryPath)) && 40 | (await asset.fs.exists(srcEntryPath)) 41 | ) { 42 | vLog(`Adding tab ${entry}`) 43 | asset.addURLDependency(`./tabs/${htmlEntry}`, { 44 | bundleBehavior: "isolated", 45 | needsStableName: true 46 | }) 47 | } 48 | }) 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/src/normalize-manifest.ts: -------------------------------------------------------------------------------- 1 | import { getState } from "./state" 2 | 3 | export const normalizeManifest = () => { 4 | const { program } = getState() 5 | delete program.$schema 6 | } 7 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/src/state.ts: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from "path" 2 | import type { Mapping } from "@mischnic/json-sourcemap" 3 | import type { 4 | MutableAsset, 5 | PluginOptions, 6 | TransformerResult 7 | } from "@parcel/types" 8 | 9 | import type { ManifestData, MV2Data } from "./schema" 10 | 11 | type ExtraAsset = TransformerResult 12 | 13 | export const storeState = ( 14 | asset: MutableAsset, 15 | program: ManifestData, 16 | ptrs: Record, 17 | options: PluginOptions 18 | ) => { 19 | const base = { 20 | extraAssets: [] as ExtraAsset[], 21 | program, 22 | hmrOptions: options.hmrOptions, 23 | hot: Boolean(options.hmrOptions), 24 | fs: asset.fs, 25 | filePath: asset.filePath, 26 | ptrs, 27 | asset, 28 | env: options.env, 29 | _isMV2: program.manifest_version === 2 30 | } 31 | const dotPlasmoDir = dirname(asset.filePath) 32 | const projectDir = resolve(dotPlasmoDir, "..") 33 | const assetsDir = resolve(projectDir, "assets") 34 | 35 | return { 36 | ...base, 37 | srcDir: process.env.PLASMO_SRC_DIR, 38 | dotPlasmoDir, 39 | projectDir, 40 | assetsDir, 41 | getAssets: () => [...base.extraAssets, asset] 42 | } 43 | } 44 | 45 | type StateParams = Parameters 46 | 47 | type State = Partial>> 48 | 49 | let state: State = {} 50 | 51 | export const getState = () => state 52 | 53 | export const initState = async (...props: StateParams) => { 54 | state = storeState(...props) 55 | } 56 | 57 | export const checkMV2 = (program: ManifestData): program is MV2Data => 58 | state._isMV2 59 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/src/utils.ts: -------------------------------------------------------------------------------- 1 | import type { DependencyOptions } from "@parcel/types" 2 | 3 | import { injectEnv } from "@plasmo/utils/env" 4 | import { wLog } from "@plasmo/utils/logging" 5 | 6 | import { getState } from "./state" 7 | 8 | export const addExtraAssets = async ( 9 | filePath: string, 10 | bundlePath: string, 11 | type = "json", 12 | dependencies = [] as DependencyOptions[] 13 | ) => { 14 | const { asset, fs, extraAssets } = getState() 15 | const rawContent = await fs.readFile(filePath, "utf8") 16 | const parsedContent = injectEnv(rawContent) 17 | extraAssets.push({ 18 | type, 19 | uniqueKey: bundlePath, 20 | content: parsedContent, 21 | bundleBehavior: "isolated", 22 | isBundleSplittable: type !== "json", 23 | env: asset.env, 24 | dependencies, 25 | meta: { 26 | bundlePath, 27 | webextEntry: false 28 | } 29 | }) 30 | } 31 | 32 | export const wLogOnce = (msg: string) => { 33 | if (!!process.env.__PLASMO_FRAMEWORK_INTERNAL_WATCHER_STARTED) { 34 | return 35 | } 36 | wLog(msg) 37 | } 38 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/src/validate-version.ts: -------------------------------------------------------------------------------- 1 | const MIN_VERSION = 0 2 | const MAX_VERSION = 65535 3 | 4 | const isInvalidVersion = (parts: string[]) => 5 | parts.some((part) => { 6 | const num = Number(part) 7 | return !isNaN(num) && num < MIN_VERSION && num > MAX_VERSION + 1 8 | }) 9 | 10 | const validateVersion = (ver: string, maxSize = 3, name = "Browser") => { 11 | const parts = ver.split(".") 12 | if (parts.length > maxSize) { 13 | return `${name} versions to have at most ${maxSize} dots` 14 | } 15 | 16 | if (isInvalidVersion(parts)) { 17 | return `${name} versions must be dot-separated integers between ${MIN_VERSION} and ${MAX_VERSION}` 18 | } 19 | 20 | return undefined 21 | } 22 | 23 | export const validateSemanticVersion = (ver: string) => 24 | validateVersion(ver, 3, "Semantic") 25 | 26 | export const validateBrowserVersion = (ver: string) => 27 | validateVersion(ver, 4, "Browser") 28 | -------------------------------------------------------------------------------- /core/parcel-transformer-manifest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/framework", 3 | "include": ["src/**/*.ts", "../../cli/plasmo/templates/plasmo.d.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-transformer-svelte/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-transformer-svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-transformer-svelte", 3 | "version": "0.6.1", 4 | "description": "Plasmo Parcel Transformer for Svelte", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup src/index.ts --minify --clean", 12 | "dev": "tsup src/index.ts --sourcemap --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.7.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "tsup": "8.4.0" 27 | }, 28 | "dependencies": { 29 | "@parcel/core": "2.9.3", 30 | "@parcel/diagnostic": "2.9.3", 31 | "@parcel/plugin": "2.9.3", 32 | "@parcel/source-map": "2.1.1", 33 | "@parcel/utils": "2.9.3", 34 | "svelte": "4.2.19" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/parcel-transformer-svelte/src/convert-error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | * 5 | * Based on: https://github.com/HellButcher/parcel-transformer-svelte3-plus 6 | * Copyright (c) 2023 Christoph Hommelsheim 7 | * MIT License 8 | */ 9 | import type { Diagnostic } from "@parcel/diagnostic" 10 | import type SourceMap from "@parcel/source-map" 11 | import type { Warning } from "svelte/types/compiler/interfaces" 12 | 13 | import { convertLOC } from "./convert-loc" 14 | import type { MutableAsset } from "./types" 15 | 16 | export function convertError( 17 | asset: MutableAsset, 18 | originalMap: SourceMap, 19 | code: string, 20 | diagnostic: Warning 21 | ) { 22 | let message = diagnostic.message || "Unknown error" 23 | if (diagnostic.code) { 24 | message = `${message} (${diagnostic.code})` 25 | } 26 | const res: Diagnostic = { 27 | message 28 | } 29 | if (diagnostic.frame) { 30 | res.hints = [diagnostic.frame] 31 | } 32 | if (diagnostic.start !== undefined && diagnostic.end !== undefined) { 33 | const { start, end } = convertLOC(asset, originalMap, diagnostic) 34 | res.codeFrames = [ 35 | { 36 | filePath: asset.filePath, 37 | code, 38 | language: "svelte", 39 | codeHighlights: [ 40 | { 41 | start, 42 | end 43 | } 44 | ] 45 | } 46 | ] 47 | } 48 | 49 | return res 50 | } 51 | -------------------------------------------------------------------------------- /core/parcel-transformer-svelte/src/convert-loc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | * 5 | * Based on: https://github.com/HellButcher/parcel-transformer-svelte3-plus 6 | * Copyright (c) 2023 Christoph Hommelsheim 7 | * MIT License 8 | */ 9 | import type SourceMap from "@parcel/source-map" 10 | import { remapSourceLocation } from "@parcel/utils" 11 | import type { Warning } from "svelte/types/compiler/interfaces" 12 | 13 | import type { MutableAsset } from "./types" 14 | 15 | export function convertLOC( 16 | asset: MutableAsset, 17 | originalMap: SourceMap, 18 | loc: Warning 19 | ) { 20 | let location = { 21 | filePath: asset.filePath, 22 | start: { 23 | line: loc.start.line + Number(asset.meta.startLine || 1) - 1, 24 | column: loc.start.column + 1 25 | }, 26 | end: { 27 | line: loc.end.line + Number(asset.meta.startLine || 1) - 1, 28 | column: loc.end.column + 1 29 | } 30 | } 31 | if (originalMap) { 32 | location = remapSourceLocation(location, originalMap) 33 | } 34 | return location 35 | } 36 | -------------------------------------------------------------------------------- /core/parcel-transformer-svelte/src/source-map.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | * 5 | * Based on: https://github.com/HellButcher/parcel-transformer-svelte3-plus 6 | * Copyright (c) 2023 Christoph Hommelsheim 7 | * MIT License 8 | */ 9 | import { dirname, isAbsolute, join } from "path" 10 | import SourceMap from "@parcel/source-map" 11 | 12 | import type { Options } from "./types" 13 | 14 | export function mapSourceMapPath(mapSourceRoot: string, sourcePath: string) { 15 | if (sourcePath.startsWith("file://")) { 16 | sourcePath = sourcePath.substring(7) 17 | } 18 | if (isAbsolute(sourcePath)) { 19 | return sourcePath 20 | } else { 21 | return join(mapSourceRoot, sourcePath) 22 | } 23 | } 24 | 25 | export function extendSourceMap( 26 | options: Options, 27 | filePath: string, 28 | originalMap: SourceMap, 29 | sourceMap: any 30 | ): SourceMap | null { 31 | if (!sourceMap) return originalMap 32 | let mapSourceRoot = dirname(filePath) 33 | let map = new SourceMap(options.projectRoot) 34 | map.addVLQMap({ 35 | ...sourceMap, 36 | sources: sourceMap.sources.map((s) => mapSourceMapPath(mapSourceRoot, s)) 37 | }) 38 | 39 | if (originalMap) { 40 | map.extends(originalMap.toBuffer()) 41 | } 42 | return map 43 | } 44 | -------------------------------------------------------------------------------- /core/parcel-transformer-svelte/src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Plasmo Corp. (https://www.plasmo.com) and contributors 3 | * MIT License 4 | */ 5 | import type { Transformer } from "@parcel/plugin" 6 | 7 | type Transform = ConstructorParameters[0]["transform"] 8 | 9 | export type MutableAsset = Parameters[0]["asset"] 10 | export type Options = Parameters[0]["options"] 11 | -------------------------------------------------------------------------------- /core/parcel-transformer-svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/cli", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /core/parcel-transformer-vue/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist/ -------------------------------------------------------------------------------- /core/parcel-transformer-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/parcel-transformer-vue", 3 | "version": "0.5.1", 4 | "description": "Plasmo Parcel Transformer for Vue", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "prepublishOnly": "pnpm build", 11 | "build": "tsup src/index.ts --minify --clean", 12 | "dev": "tsup src/index.ts --sourcemap --watch" 13 | }, 14 | "author": "Plasmo Corp. ", 15 | "homepage": "https://docs.plasmo.com/", 16 | "engines": { 17 | "parcel": ">= 2.7.0" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/PlasmoHQ/plasmo.git" 23 | }, 24 | "devDependencies": { 25 | "@plasmo/config": "workspace:*", 26 | "tsup": "8.4.0" 27 | }, 28 | "dependencies": { 29 | "@parcel/core": "2.9.3", 30 | "@parcel/diagnostic": "2.9.3", 31 | "@parcel/plugin": "2.9.3", 32 | "@parcel/source-map": "2.1.1", 33 | "@parcel/types": "2.9.3", 34 | "@parcel/utils": "2.9.3", 35 | "@plasmohq/consolidate": "0.17.0", 36 | "@vue/compiler-sfc": "3.5.13", 37 | "nullthrows": "1.1.1", 38 | "semver": "7.7.1", 39 | "vue": "3.5.13" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/parcel-transformer-vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/cli", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import tseslint from "typescript-eslint"; 3 | 4 | 5 | /** @type {import('eslint').Linter.Config[]} */ 6 | export default [ 7 | {files: ["**/*.{js,mjs,cjs,ts}"]}, 8 | {languageOptions: { globals: {...globals.browser, ...globals.node} }}, 9 | ...tseslint.configs.recommended, 10 | ]; -------------------------------------------------------------------------------- /packages/framework-shared/build-socket/event.ts: -------------------------------------------------------------------------------- 1 | export enum BuildSocketEvent { 2 | BuildReady = "build_ready", 3 | CsChanged = "cs_changed" 4 | } 5 | -------------------------------------------------------------------------------- /packages/framework-shared/build-socket/index.ts: -------------------------------------------------------------------------------- 1 | import { WebSocket, WebSocketServer } from "ws" 2 | 3 | import { BuildSocketEvent } from "./event" 4 | 5 | export { BuildSocketEvent } 6 | 7 | const createBuildSocket = (hmrHost: string, hmrPort: number) => { 8 | const wss = new WebSocketServer({ 9 | host: hmrHost, 10 | port: hmrPort + 1 11 | }) 12 | 13 | const broadcast = (type: BuildSocketEvent) => { 14 | for (const client of wss.clients) { 15 | if (client.readyState === WebSocket.OPEN) { 16 | client.send(JSON.stringify({ type })) 17 | } 18 | } 19 | } 20 | 21 | return { 22 | broadcast 23 | } 24 | } 25 | 26 | let _buildSocket: Awaited> 27 | 28 | export const getBuildSocket = (hmrHost = "localhost", hmrPort?: number) => { 29 | if (process.env.NODE_ENV === "production") { 30 | return null 31 | } 32 | 33 | if (!!_buildSocket) { 34 | return _buildSocket 35 | } 36 | 37 | if (!hmrPort) { 38 | throw new Error("HMR port is not provided") 39 | } 40 | 41 | _buildSocket = createBuildSocket(hmrHost, hmrPort) 42 | return _buildSocket 43 | } 44 | 45 | export const buildBroadcast = (type: BuildSocketEvent) => { 46 | if (process.env.NODE_ENV === "production") { 47 | return 48 | } 49 | 50 | const buildSocket = getBuildSocket() 51 | if (buildSocket) { 52 | buildSocket.broadcast(type) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/framework-shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmo/framework-shared", 3 | "version": "0.0.2", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies": { 10 | "ws": "8.18.1" 11 | }, 12 | "devDependencies": { 13 | "@plasmo/config": "workspace:*", 14 | "@plasmo/utils": "workspace:*" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/framework-shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@plasmo/config/ts/utils", 3 | "include": ["./**/*.ts"], 4 | "exclude": ["node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/init/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /packages/init/bpp.yml: -------------------------------------------------------------------------------- 1 | name: "Submit to Web Store" 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Cache pnpm modules 11 | uses: actions/cache@v3 12 | with: 13 | path: ~/.pnpm-store 14 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 15 | restore-keys: | 16 | ${{ runner.os }}- 17 | - uses: pnpm/action-setup@v2.4.1 18 | with: 19 | version: latest 20 | run_install: true 21 | - name: Use Node.js 20.x 22 | uses: actions/setup-node@v3.5.1 23 | with: 24 | node-version: 20.x 25 | cache: "pnpm" 26 | - name: Build the extension 27 | run: pnpm build 28 | - name: Package the extension into a zip artifact 29 | run: pnpm package 30 | - name: Browser Platform Publish 31 | uses: PlasmoHQ/bpp@v3 32 | with: 33 | keys: ${{ secrets.SUBMIT_KEYS }} 34 | artifact: build/chrome-mv3-prod.zip 35 | -------------------------------------------------------------------------------- /packages/init/entries/background.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | console.log("Hello from background script!") 4 | -------------------------------------------------------------------------------- /packages/init/entries/content.ts: -------------------------------------------------------------------------------- 1 | import type { PlasmoCSConfig } from "plasmo" 2 | 3 | export const config: PlasmoCSConfig = { 4 | matches: ["https://www.plasmo.com/*"] 5 | } 6 | 7 | window.addEventListener("load", () => { 8 | console.log("content script loaded") 9 | 10 | document.body.style.background = "pink" 11 | }) 12 | -------------------------------------------------------------------------------- /packages/init/entries/contents/inline.tsx: -------------------------------------------------------------------------------- 1 | import type { PlasmoCSConfig, PlasmoGetInlineAnchor } from "plasmo" 2 | 3 | export const config: PlasmoCSConfig = { 4 | matches: ["https://www.plasmo.com/*"] 5 | } 6 | 7 | export const getInlineAnchor: PlasmoGetInlineAnchor = () => 8 | document.querySelector("#supercharge > h2 > span") 9 | 10 | // Use this to optimize unmount lookups 11 | export const getShadowHostId = () => "plasmo-inline-example-unique-id" 12 | 13 | const PlasmoInline = () => { 14 | return 15 | } 16 | 17 | export default PlasmoInline 18 | -------------------------------------------------------------------------------- /packages/init/entries/contents/overlay.tsx: -------------------------------------------------------------------------------- 1 | import type { PlasmoCSConfig, PlasmoGetOverlayAnchor } from "plasmo" 2 | 3 | export const config: PlasmoCSConfig = { 4 | matches: ["https://www.plasmo.com/*"] 5 | } 6 | 7 | export const getOverlayAnchor: PlasmoGetOverlayAnchor = async () => 8 | document.querySelector("#pricing") 9 | 10 | const PlasmoPricingExtra = () => { 11 | return ( 12 | 17 | HELLO WORLD 18 | 19 | ) 20 | } 21 | 22 | export default PlasmoPricingExtra 23 | -------------------------------------------------------------------------------- /packages/init/entries/newtab.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | function IndexNewtab() { 4 | const [data, setData] = useState("") 5 | 6 | return ( 7 |
13 |

14 | Welcome to your{" "} 15 | 16 | Plasmo 17 | {" "} 18 | Extension! 19 |

20 | setData(e.target.value)} value={data} /> 21 | 22 | View Docs 23 | 24 |
25 | ) 26 | } 27 | 28 | export default IndexNewtab 29 | -------------------------------------------------------------------------------- /packages/init/entries/options.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | function IndexOptions() { 4 | const [data, setData] = useState("") 5 | 6 | return ( 7 |
13 |

14 | Welcome to your{" "} 15 | 16 | Plasmo 17 | {" "} 18 | Extension! 19 |

20 | setData(e.target.value)} value={data} /> 21 | 22 | View Docs 23 | 24 |
25 | ) 26 | } 27 | 28 | export default IndexOptions 29 | -------------------------------------------------------------------------------- /packages/init/entries/popup.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | function IndexPopup() { 4 | const [data, setData] = useState("") 5 | 6 | return ( 7 |
13 |

14 | Welcome to your{" "} 15 | 16 | Plasmo 17 | {" "} 18 | Extension! 19 |

20 | setData(e.target.value)} value={data} /> 21 | 22 | View Docs 23 | 24 |
25 | ) 26 | } 27 | 28 | export default IndexPopup 29 | -------------------------------------------------------------------------------- /packages/init/index.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/init/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plasmohq/init", 3 | "version": "0.7.0", 4 | "description": "Plasmo init template files", 5 | "files": [ 6 | "entries", 7 | "templates", 8 | "bpp.yml" 9 | ], 10 | "main": "index.json", 11 | "author": "Plasmo Corp. ", 12 | "homepage": "https://docs.plasmo.com/", 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/PlasmoHQ/plasmo.git" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/init/templates/README.md: -------------------------------------------------------------------------------- 1 | This is a [Plasmo extension](https://docs.plasmo.com/) project bootstrapped with [`plasmo init`](https://www.npmjs.com/package/plasmo). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | pnpm dev 9 | # or 10 | npm run dev 11 | ``` 12 | 13 | Open your browser and load the appropriate development build. For example, if you are developing for the chrome browser, using manifest v3, use: `build/chrome-mv3-dev`. 14 | 15 | You can start editing the popup by modifying `popup.tsx`. It should auto-update as you make changes. To add an options page, simply add a `options.tsx` file to the root of the project, with a react component default exported. Likewise to add a content page, add a `content.ts` file to the root of the project, importing some module and do some logic, then reload the extension on your browser. 16 | 17 | For further guidance, [visit our Documentation](https://docs.plasmo.com/) 18 | 19 | ## Making production build 20 | 21 | Run the following: 22 | 23 | ```bash 24 | pnpm build 25 | # or 26 | npm run build 27 | ``` 28 | 29 | This should create a production bundle for your extension, ready to be zipped and published to the stores. 30 | 31 | ## Submit to the webstores 32 | 33 | The easiest way to deploy your Plasmo extension is to use the built-in [bpp](https://github.com/marketplace/actions/browser-platform-publisher) GitHub action. Prior to using this action however, make sure to build your extension and upload the first version to the store to establish the basic credentials. Then, simply follow [this setup instruction](https://docs.plasmo.com/framework/workflows/submit) and you should be on your way for automated submission! 34 | -------------------------------------------------------------------------------- /packages/init/templates/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlasmoHQ/plasmo/9369e2835de237caa60620b74b12ea3e2d4f3600/packages/init/templates/assets/icon.png -------------------------------------------------------------------------------- /packages/init/templates/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "plasmo/templates/tsconfig.base", 3 | "exclude": ["node_modules"], 4 | "include": [".plasmo/index.d.ts", "./**/*.ts", "./**/*.tsx"], 5 | "compilerOptions": { 6 | "paths": { 7 | "~*": ["./*"] 8 | }, 9 | "baseUrl": "." 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/init/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./**/*.ts", "./**/*.tsx"], 3 | "compilerOptions": { 4 | "strict": false, 5 | "jsx": "react-jsx", 6 | "paths": { 7 | "plasmo": ["../../cli/plasmo/src/type.ts"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "cli/*" 3 | - "packages/*" 4 | - "examples/*" 5 | - "api/*" 6 | - "core/*" 7 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | "group:allNonMajor" 6 | ], 7 | "git-submodules": { 8 | "enabled": true 9 | }, 10 | "cloneSubmodules": true 11 | } -------------------------------------------------------------------------------- /scripts/move-prettier-cjs-to-mjs.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # a bash script that traverse the examples directory and mv all prettier cjs files to mjs 4 | 5 | dir="examples" 6 | 7 | # Use a for loop to traverse the directory 8 | for subdir in $(find $dir -type d); do 9 | # Check if the file exists 10 | if [ -f "$subdir/.prettierrc.cjs" ]; then 11 | # If the file exists, rename it 12 | mv "$subdir/.prettierrc.cjs" "$subdir/.prettierrc.mjs" 13 | echo "Renamed .prettierrc.cjs to .prettierrc.mjs in directory $subdir" 14 | fi 15 | done 16 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "tasks": { 4 | "build": { 5 | "dependsOn": [ 6 | "^build" 7 | ], 8 | "outputs": [ 9 | "dist/**" 10 | ] 11 | }, 12 | "publish": { 13 | "dependsOn": [ 14 | "^build" 15 | ], 16 | "outputs": [ 17 | "dist/**" 18 | ] 19 | }, 20 | "lint": { 21 | "outputs": [] 22 | }, 23 | "dev": { 24 | "dependsOn": [ 25 | "^build" 26 | ], 27 | "cache": false 28 | }, 29 | "clean": { 30 | "dependsOn": [ 31 | "^clean" 32 | ], 33 | "cache": false 34 | } 35 | } 36 | } 37 | --------------------------------------------------------------------------------