vitebrowser-extensionastrojs

Bundle code as a separate js file in Astro (astro.build, astrojs)


I was wondering if there was way to have a src/somescript.ts to participate in bundling but to be output as a separate file in the build dist/somescript.ts not referenced by a page.

When building a browser extension we can reference a content_scripts file that will be used to load into the current activeTab

"content_scripts": [
    {
      "matches": ["https://*/*"],
      "js": ["content.js"],
      "run_at": "document_end"
    }
  ],

I have tried to add a script on the public/content.js and tried to import a src/somescript.ts but it just copies it verbatim.

Is there a way to configure Astro to bundle separate js files from src/somescript.ts?


Solution

  • To achieve your goal you can use Vite Js API and create custom Node script that handles extension files separately. It should get executed after default build script finishes its job.

    Let's add Vite JS API to our dev-dependencies:

    package.json

      "devDependencies": {
        .
        "vite": "4.3.0"
      },
    

    Then create that custom script which takes advantage of Vite build:

    /* 
        build.mjs
        Custom Vite build script for extension files,
        to execute after default build finishes its work.
    */
    import { build } from 'vite';
    import path from 'path';
    import { fileURLToPath } from 'url';
    
    const __dirname = path.dirname(fileURLToPath(import.meta.url));
    
    const extScripts = [
        {
            entry: path.resolve(__dirname, 'src-browser-ext/client-script.ts'),
            fileName: 'client-script'
        },
        /*
            If later you decide to add a background service worker.
        */
        // {
        //  entry: path.resolve(__dirname, 'src-browser-ext/background-sw.ts'),
        //  fileName: 'background-sw'
        // }
    ];
    
    extScripts.forEach(async (scr) => {
        await build({
            build: {
                // Weather to add sourcemap or not
                // make it false if not required
                sourcemap: 'inline',
                outDir: './dist',
                lib: {
                    ...scr,
                    formats: ['es']
                },
                emptyOutDir: false
            },
            configFile: false
        });
    });
    
    

    As you can see I would keep my extension scripts in a separate source folder: src-browser-ext

    Now we need to integrate this to our build process, add a new entry for scripts property in your project's package.json.

        "scripts": {
            .
            .
            "build-ext": "astro build && node build.mjs"
        },
    

    That's it. Now instead of npm run build (or pnpm run build, whatever) do npm run build-ext.

    However there is a high chance that you face other problems after, like inline scripts injected by Astro causing security errors upon extension execution. I've been trying to use SvelteKit, also uses Vite like Astro does, and after its latest changes unless they implement a new option (which is in the queue for some time) there is no way it can be used for extension development.

    AFAIK Astro has the same problem in some situations. See the docs. The solution they offer there (updating CSP for unsafe-inline) is not good for browser extensions, at least for now.

    Also I'd like to point another setting people need to change when they want to use a framework like Astro (or SvelteKit), these frameworks tend to output final artifacts in a folder that its name starts with an underscore _. Extensions can not reach folders with their name starting an underscore. The setting you need to change in Astro is build.assets, in the example below I change it to myapp, default is _astro:

    astro.config.mjs

    export default defineConfig({
      build : {
        assets: 'myapp'
      }
    });