Search code examples
gitlabcorsgitlab-cisveltestorybook

Storybook Gitlab Pages: script importing from wrong URL resulting in CORS errors


I am trying to use Gitlab CI to build and host a storybook page when I push an update to any branch. Here is the current CI script:

CI Script

image: node:16

stages:
  - setup
  - build-and-test
  - deployment
  - pages

# stage: setup

setup:
  stage: setup
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
  artifacts:
    paths:
      - node_modules/
  script:
    - yarn

# stage: build-and-test (here I run linter, unit tests, and everything I want to build or test)

build:
  stage: build-and-test
  artifacts:
    paths:
      - dist/
  script:
    - yarn build

storybook:
  stage: build-and-test
  artifacts:
    expire_in: 2 weeks
    when: always
    paths:
      - storybook/
  script:
    - yarn build-storybook --output-dir storybook

# stage: deployment (here I deploy my app to specific stages or other artefacts like storybook)

deploy-storybook:
  stage: deployment
  script:
    - echo "Enjoy the day. 🥳 Every job needs a script, but this job was just created to configure an environment."
  environment:
    name: storybook/$CI_COMMIT_REF_SLUG
    url: https://<my_username>.gitlab.io/<my_project>/$CI_COMMIT_REF_SLUG/storybook/
    on_stop: remove-storybook
  only:
    - branches

remove-storybook:
  stage: deployment
  cache:
    key: 'my-storybook'
    paths:
      - public
  script:
    - rm -rf "public/$CI_COMMIT_REF_SLUG/storybook"
  when: manual
  variables:
    GIT_STRATEGY: none # needed to prevent "Couldn't find remote ref" error
  environment:
    name: storybook/$CI_COMMIT_REF_SLUG
    action: stop

# stage: pages (the stage name is custom, but the job NEEDS to be named pages)

pages:
  stage: pages
  cache:
    key: 'my-storybook'
    paths:
      - public
  script:
    - if [ "$CI_COMMIT_REF_NAME" = "main" ]; then
      mkdir -p public;
      touch public/index.html;
      echo "<!DOCTYPE HTML><script>window.location.href = 'https://<my_username>.gitlab.io/<my_project>/main/storybook'</script>" > public/index.html;
      fi;
    - rm -rf "public/$CI_COMMIT_REF_SLUG"
    - mkdir -p "public/$CI_COMMIT_REF_SLUG";
    - mv storybook "public/$CI_COMMIT_REF_SLUG"
  artifacts:
    paths:
      - public

This compiles as intended but has errors in console when viewed:

iframe.html:1 Access to script at 'https://gitlab.com/oauth/authorize?client_id=<my_client_id>&redirect_uri=https://projects.gitlab.io/auth&response_type=code&state=AFyDa1QTpsd9qXqSzdoy4w==&scope=api' (redirected from 'https://<my_username>.gitlab.io/assets/iframe.8696a5de.js') from origin 'https://<my_username>.gitlab.io' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
iframe.html:374          GET https://gitlab.com/oauth/authorize?client_id=<my_client_id>&redirect_uri=https://projects.gitlab.io/auth&response_type=code&state=AFyDa1QTpsd9qXqSzdoy4w==&scope=api net::ERR_FAILED 302

The relevant section of https://<my_username>.gitlab.io/<project_name>/<branch>/storybook/assets/iframe.html:

<head>
  ...
  <script defer src="/node_modules/@fortawesome/fontawesome-free/js/all.min.js"></script>

  <!-- iframe.html:374 -->
  <script type="module" crossorigin src="/assets/iframe.8696a5de.js"></script>

  <link rel="stylesheet" href="/assets/iframe.0f83afa4.css">
  ...
</head>

.storybook/main.cjs

const { resolve } = require("path");
const { typescript: preprocessTS } = require("svelte-preprocess");
const { loadConfigFromFile, mergeConfig } = require("vite");

module.exports = {
  "stories": [
    "../src/**/*.stories.mdx",
    "../src/**/*.stories.@(js|jsx|ts|tsx|svelte)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
    "@storybook/addon-svelte-csf"
  ],
  "framework": "@storybook/svelte",
  "core": {
    "builder": "@storybook/builder-vite"
  },
  "svelteOptions": {
    "preprocess": import("../svelte.config.js").preprocess
  },
  svelteOptions: {
    preprocess: [
      preprocessTS(),
    ],
  },
  async viteFinal(config, { configType }) {
    const { config: userConfig } = await loadConfigFromFile(
      resolve(__dirname, "../vite.config.ts")
    );

    return mergeConfig(config, {
      ...userConfig,
      // manually specify plugins to avoid conflict
      plugins: []
    });
  }
}

Question

What can I do to fix this script import error? I think the generated url in the script tag is wrong, and should instead be something similar to https://<my_username>.gitlab.io/<project_name>/<branch>/storybook/assets/iframe.8696a5de.js (instead of the current https://<my_username>.gitlab.io/assets/iframe.8696a5de.js). Is there any way I can edit the CI to edit the root URL?

If that would not work, why not, and what would work instead?


Solution

  • I have found a workaround but I would not consider it a proper solution. If you are stuck with this problem, the following does work:

    I used Regular Expression replacement in the GitLab CI to correct the src and href paths of all links in the built index.html and iframe.html files. The exact code I used is as follows:

    - sed -i -E "s/((src|href)=\")\/?/\1.\//g" public/$CI_COMMIT_REF_SLUG/storybook/index.html
    - sed -i -E "s/((src|href)=\")\/?/\1.\//g" public/$CI_COMMIT_REF_SLUG/storybook/iframe.html
    

    This change is necessary for 2 reasons:

    • The page hosted includes the repository name (eg, <user>.gitlab.io/<repo>/). Any links from this page starting in a slash will search for their resources at <user>.gitlab.io/ and the resource won't be there, but the system never gets that far (see below).
    • Gitlab requires authentication for any non-relative resource paths so the resource will be blocked.

    The regex replacement fixes these problems by replacing any resource written as path/name.ext or /path/name.ext with ./path/name.ext which in my case works and shall hopefully work for whoever needs this.