shubh
shubh

Reputation: 173

Trying to build vue component library using rollup and vueJs 3

I am trying to create a vue component library using rollup and vuejs. It worked with vue2 but was unable to parse css with vue3. I have upgraded the dependency in package.json

package.json

{
  "name": "vue2tslibrary",
  "version": "0.1.59",
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "build:js": "rimraf dist && rollup -c && rollup -c --environment MINIFY"
  },
  "sideEffects": [
    "*.css",
    "*.scss"
  ],
  "files": [
    "dist",
    "src"
  ],
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "unpkg": "dist/vueslib.min.js",
  "dependencies": {
    "@mathieustan/vue-datepicker": "^0.2.8",
    "core-js": "^3.6.5",
    "date-fns": "^2.16.1",
    "vue": "^3.0.1",
    "vue-router": "^4.0.0-beta.13",
    "vue-slider-component": "^4.0.0-beta.2",
    "vue-template-compiler": "^2.6.12",
    "vuex": "^4.0.0-beta.4"
  },
  "devDependencies": {
    "@babel/plugin-proposal-optional-chaining": "^7.12.1",
    "@rollup/plugin-alias": "2.2.0",
    "@rollup/plugin-babel": "^5.2.1",
    "@rollup/plugin-commonjs": "^15.1.0",
    "@rollup/plugin-image": "^2.0.5",
    "@rollup/plugin-node-resolve": "^9.0.0",
    "@rollup/plugin-url": "^5.0.1",
    "@typescript-eslint/eslint-plugin": "^2.33.0",
    "@typescript-eslint/parser": "^2.33.0",
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-plugin-router": "~4.5.0",
    "@vue/cli-plugin-typescript": "~4.5.0",
    "@vue/cli-plugin-vuex": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.1",
    "@vue/eslint-config-standard": "^5.1.2",
    "@vue/eslint-config-typescript": "^5.0.2",
    "autoprefixer": "^9.8.6",
    "cssnano": "^4.1.10",
    "eslint": "^6.7.2",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^6.2.2",
    "node-sass": "^4.14.1",
    "postcss": "^8.1.1",
    "postcss-calc": "^7.0.3",
    "postcss-color-function": "^4.1.0",
    "postcss-cssnext": "^3.1.0",
    "postcss-discard-comments": "^4.0.2",
    "postcss-discard-empty": "^4.0.1",
    "postcss-discard-unused": "^4.0.1",
    "postcss-each": "^0.10.0",
    "postcss-extend-rule": "^3.0.0",
    "postcss-import": "^12.0.1",
    "postcss-mixins": "^6.2.3",
    "postcss-nested": "^4.2.1",
    "postcss-rem": "^1.1.5",
    "postcss-simple-vars": "^5.0.2",
    "postcss-sort-media-queries": "^1.7.26",
    "postcss-url": "^8.0.0",
    "rollup": "1.17.0",
    "rollup-plugin-analyzer": "^3.3.0",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-commonjs": "^10.1.0",
    "rollup-plugin-css-only": "^2.1.0",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-postcss": "^3.1.8",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript": "^1.0.1",
    "rollup-plugin-typescript2": "^0.28.0",
    "rollup-plugin-uglify": "^6.0.4",
    "rollup-plugin-vue": "^6.0.0-beta.8",
    "sass-loader": "^10.0.3",
    "style-resources-loader": "1.3.3",
    "typescript": "~3.9.3"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "@vue/standard",
      "@vue/typescript/recommended"
    ],
    "parserOptions": {
      "ecmaVersion": 2020
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

rollup.config.js

import vue from 'rollup-plugin-vue'
import node from '@rollup/plugin-node-resolve'
import cjs from '@rollup/plugin-commonjs'
import babel from '@rollup/plugin-babel'
import postcss from 'rollup-plugin-postcss'
import { terser } from 'rollup-plugin-terser'
import css from 'rollup-plugin-css-only'
import postcssImport from 'postcss-import'
import autoprefixer from 'autoprefixer'
import simplevars from 'postcss-simple-vars'
import nested from 'postcss-nested'
import postcssEach from 'postcss-each'
import postcssMixin from 'postcss-mixins'
import postcssColor from 'postcss-color-function'
import postcssCalc from 'postcss-calc'
import postcssextend from 'postcss-extend-rule'
import postcssDiscardComment from 'postcss-discard-comments'
import postcssDiscardEmpty from 'postcss-discard-empty'
import postcssUrl from 'postcss-url'
import postcssRem from 'postcss-rem'
import sortMedia from 'postcss-sort-media-queries'
import cssnano from 'cssnano'
import url from '@rollup/plugin-url'
import typescript from 'rollup-plugin-typescript2'
import analyze from 'rollup-plugin-analyzer'

import fs from 'fs'
import path from 'path'

const babelConfig = {
  exclude: 'node_modules/**',
  babelHelpers: true,
  babelrc: false,
  presets: [['@babel/preset-env', { modules: false }]]
}

const baseFolder = './src/'
const componentsFolder = 'components/'

const components = fs
  .readdirSync(baseFolder + componentsFolder)
  .filter((f) =>
    fs.statSync(path.join(baseFolder + componentsFolder, f)).isDirectory()
  )

const entries = {
  index: './src/index.ts',
  ...components.reduce((obj, name) => {
    obj[name] = (baseFolder + componentsFolder + name)
    return obj
  }, {})
}

const capitalize = (s) => {
  if (typeof s !== 'string') return ''
  return s.charAt(0).toUpperCase() + s.slice(1)
}

const vuePluginConfig = {
  defaultLang: {
    style: 'postcss',
    script: 'ts'
  },
  transformAssetUrls: {
    includeAbsolute: true
  },
  preProcessStyles: true,
  compileTemplate: false,
  template: {
    isProduction: true,
    compilerOptions: {
      whitespace: 'condense'
    }
  },
  style: {
    postcssPlugins: [
      autoprefixer,
      postcssImport({
        resolve (id, basedir) {
          // resolve alias @css, @import '@css/style.css'
          // because @css/ has 5 chars
          if (id.startsWith('@css')) {
            // basedir will resolve to /src/components
            return path.resolve('src/assets/styles/css', id.slice(5))
          }

          // resolve node_modules, @import '~normalize.css/normalize.css'
          // similar to how css-loader's handling of node_modules
          // if (id.startsWith('~')) {
          //   return path.resolve(basedir, '../node_modules', id);
          // }

          // resolve relative path, @import './components/style.css'
          return path.resolve(basedir, id)
        }
      }),
      postcssEach,
      postcssMixin,
      simplevars,
      postcssColor,
      postcssCalc,
      nested,
      postcssextend,
      postcssDiscardComment,
      postcssDiscardEmpty,
      postcssUrl({ url: 'inline' }),
      postcssRem({
        baseline: 16, // Default to 16
        // convert: 'px', // Default to rem
        fallback: true, // Default to false
        precision: 6 // Default to 5
      }),
      sortMedia({
        sort: 'mobile-first'
      }),
      autoprefixer({
        overrideBrowserslist: '> 1%, IE 6, Explorer >= 10, Safari >= 7'
      }),
      cssnano({
        zindex: false
      })

    ]
  }
}

export default () => {
  const mapComponent = (name) => {
    return [
      {
        input: baseFolder + componentsFolder + `${name}/index.ts`,
        external: ['vue'],
        output: {
          format: 'umd',
          name: capitalize(name),
          file: `dist/components/${name}/index.js`,
          exports: 'named',
          globals: {
            vue: 'Vue'
          }
        },
        plugins: [
          typescript(),
          url({
            include: [
              '**/*.svg',
              '**/*.png',
              '**/*.gif',
              '**/*.jpg',
              '**/*.jpeg'
            ]
          }),
          node({
            extensions: ['.vue', '.js', '.ts']
          }),
          cjs(),
          vue(vuePluginConfig),
          css(),
          babel(babelConfig)
        ]
      }
    ]
  }

  let config = [
    {
      input: entries,
      external: ['vue'],
      output: {
        format: 'esm',
        dir: 'dist/esm'
      },
      plugins: [
        typescript(),
        cjs(),
        url({
          include: [
            '**/*.svg',
            '**/*.png',
            '**/*.gif',
            '**/*.jpg',
            '**/*.jpeg'
          ]
        }),
        node({
          extensions: ['.vue', '.js', '.ts']
        }),
        vue(vuePluginConfig),
        css(),
        babel(babelConfig),
        analyze(),
        terser({
          output: {
            comments: '/^!/'
          },
          compress: {
            defaults: true
          }
        })
      ]
    },
    {
      input: entries,
      external: ['vue'],
      output: {
        format: 'cjs',
        dir: 'dist/cjs',
        exports: 'named'
      },
      plugins: [
        typescript(),
        url({
          include: [
            '**/*.svg',
            '**/*.png',
            '**/*.gif',
            '**/*.jpg',
            '**/*.jpeg'
          ]
        }),
        node({
          extensions: ['.vue', '.js', '.ts']
        }),
        vue(vuePluginConfig),
        css(),
        babel(babelConfig),
        cjs()
      ]
    },
    // {
    //   input: 'src/index.ts',
    //   external: ['vue'],
    //   output: {
    //     format: 'umd',
    //     name: capitalize('vu'),
    //     file: 'dist/vueslib.js',
    //     exports: 'named',
    //     globals: {
    //       vue: 'Vue'
    //     }
    //   },
    //   plugins: [
    //     typescript(),
    //     url({
    //       include: [
    //         '**/*.svg',
    //         '**/*.png',
    //         '**/*.gif',
    //         '**/*.jpg',
    //         '**/*.jpeg'
    //       ]
    //     }),
    //     node({
    //       extensions: ['.vue', '.js', '.ts']
    //     }),
    //     vue(vuePluginConfig),
    //     babel(babelConfig),
    //     cjs()
    //   ]
    // },
    {
      input: 'src/index.ts',
      external: ['vue'],
      output: {
        format: 'esm',
        file: 'dist/vueslib.esm.js'
      },
      plugins: [
        typescript(),
        url({
          include: [
            '**/*.svg',
            '**/*.png',
            '**/*.gif',
            '**/*.jpg',
            '**/*.jpeg'
          ]
        }),
        node({
          extensions: ['.vue', '.js', '.ts', '.css']
        }),
        vue(vuePluginConfig),
        css(),
        babel(babelConfig),
        cjs()
      ]
    },
    // individual components
    ...components.map((f) => mapComponent(f)).reduce((r, a) => r.concat(a), [])

  ]

  if (process.env.MINIFY === 'true') {
    config = config.filter((c) => !!c.output.file)
    config.forEach((c) => {
      c.output.file = c.output.file.replace(/\.js/g, '.min.js')
      c.plugins.push(terser({
        output: {
          comments: '/^!/'
        },
        compress: {
          defaults: true
        }
      }))
    })
  }
  return config
}

ERROR SCREENSHOT Error while building library using rollup config

Component

<template>
<!-- @css/_app-partials.css-->
<div>
    <p class="yellow" @click="test()"> I am Dummy Component
      <span class="reda">asdasdas</span>
    </p>
    <span class="red">asdasdas</span>
    <img :src="require('../../assets/img/icons/download.jpeg')" />
    <div class="imgtest">
    </div>
</div>
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue'

export default defineComponent({
  name: 'DummyComponent',
  data () {
    return {
      text1: 'I am Text Component'
    }
  },
  props: {
    isValid: {
      type: Boolean,
      default: false
    },
    msg: {
      type: Object as PropType<{foo: string; bar: string}>
    }
  },
  methods: {
    test (): void {
      console.log('test1', this.$props.msg)
    }
  }
})

</script>

<style lang="postcss" scoped>
@import '../../assets/styles/css/_app-partials.css';

.yellow {
    color: red;

    .reda {
        color: $black;
    }
}

.red {
    color: green;
}

.imgtest {
    background-image: url('../../assets/img/icons/star-half.svg');
    background-size: 100%;
    width: 100px;
    height: 100px;
}
</style>

github repository link https://github.com/shubhadip/vue-typescript-component-library

Upvotes: 1

Views: 6786

Answers (2)

shubh
shubh

Reputation: 173

I got this working by changing lang="postcss" to lang="css" and then tweaking rollup-plugin-vue options which inlined the css on the components and to generate css in separate file had to use rollup-plugin-css-only.Working code is there in Github

Upvotes: 1

Daniel
Daniel

Reputation: 35684

Looks like (at the time of writing) rollup vue plgin is not supporting vue3 and scss fully.

github issue

First aid

The way around this was suggested by @P-Seebauer's comment (above). I added rollup-plugin-scss to the mix, and it seems to please Rollup. The build proceeds. It handles both .scss and .css files.

However, since Vue.js is expected to handle these, asking application developers to add such a plugin seems unnecessary.

You can also see @akauppi's answer at: Rollup, Vue and Buble, unexpected token in scss file (though that seems vue2 specific)

Try this:

$ npm install --save-dev rollup-plugin-scss

In rollup.config.js:

import scss from 'rollup-plugin-scss';      // handles '.css' and '.scss'
plugins: { ..., scss() }

I don't really know what's going on, here. The above worked for me (Vue.js 3 (beta) and rollup-plugin-vue 6.0.0-beta.6.

Upvotes: 0

Related Questions