vitejs / vite

Next generation frontend tooling. It's fast!
http://vite.dev
MIT License
68.16k stars 6.15k forks source link

Add extract css false option in lib build mode #4345

Open JackFGreen opened 3 years ago

JackFGreen commented 3 years ago

Clear and concise description of the problem

When buliding a lib, css will be extracted in a single file style.css, is there a way to bundle css in output js.

Suggested solution

Like rollup-plugin-postcss can set extract

Alternative

No response

Additional context

No response

Validations

emosheeep commented 3 years ago

I have the same problem which I suffer from..

kooriookami commented 3 years ago

Me too

falstack commented 3 years ago

Me too

xinyao27 commented 3 years ago

I encountered the same problem, css cannot be loaded correctly when building lib mode.

I created a plugin to temporarily solve this problem.

import fs from 'fs'
import { resolve } from 'path'
import type { ResolvedConfig, PluginOption } from 'vite'

const fileRegex = /\.(css)$/

const injectCode = (code: string) =>
  `function styleInject(css,ref){if(ref===void 0){ref={}}var insertAt=ref.insertAt;if(!css||typeof document==="undefined"){return}var head=document.head||document.getElementsByTagName("head")[0];var style=document.createElement("style");style.type="text/css";if(insertAt==="top"){if(head.firstChild){head.insertBefore(style,head.firstChild)}else{head.appendChild(style)}}else{head.appendChild(style)}if(style.styleSheet){style.styleSheet.cssText=css}else{style.appendChild(document.createTextNode(css))}};styleInject(\`${code}\`)`
const template = `console.warn("__INJECT__")`

let viteConfig: ResolvedConfig
const css: string[] = []

export default function libInjectCss(): PluginOption {
  return {
    name: 'lib-inject-css',

    apply: 'build',

    configResolved(resolvedConfig: ResolvedConfig) {
      viteConfig = resolvedConfig
    },

    transform(code: string, id: string) {
      if (fileRegex.test(id)) {
        css.push(code)
        return {
          code: '',
        }
      }
      if (
        // @ts-ignore
        id.includes(viteConfig.build.lib.entry)
      ) {
        return {
          code: `${code}
          ${template}`,
        }
      }
      return null
    },

    async writeBundle(_: any, bundle: any) {
      for (const file of Object.entries(bundle)) {
        const { root } = viteConfig
        const outDir: string = viteConfig.build.outDir || 'dist'
        const fileName: string = file[0]
        const filePath: string = resolve(root, outDir, fileName)

        try {
          let data: string = fs.readFileSync(filePath, {
            encoding: 'utf8',
          })

          if (data.includes(template)) {
            data = data.replace(template, injectCode(css.join('\n')))
          }

          fs.writeFileSync(filePath, data)
        } catch (e) {
          console.error(e)
        }
      }
    },
  }
}

https://github.com/ohbug-org/ohbug-extension-feedback/blob/main/libInjectCss.ts

jackchoumine commented 3 years ago

I encountered the same problem, css cannot be loaded correctly when building lib mode.

I created a plugin to temporarily solve this problem.

import fs from 'fs'
import { resolve } from 'path'
import type { ResolvedConfig, PluginOption } from 'vite'

const fileRegex = /\.(css)$/

const injectCode = (code: string) =>
  `function styleInject(css,ref){if(ref===void 0){ref={}}var insertAt=ref.insertAt;if(!css||typeof document==="undefined"){return}var head=document.head||document.getElementsByTagName("head")[0];var style=document.createElement("style");style.type="text/css";if(insertAt==="top"){if(head.firstChild){head.insertBefore(style,head.firstChild)}else{head.appendChild(style)}}else{head.appendChild(style)}if(style.styleSheet){style.styleSheet.cssText=css}else{style.appendChild(document.createTextNode(css))}};styleInject(\`${code}\`)`
const template = `console.warn("__INJECT__")`

let viteConfig: ResolvedConfig
const css: string[] = []

export default function libInjectCss(): PluginOption {
  return {
    name: 'lib-inject-css',

    apply: 'build',

    configResolved(resolvedConfig: ResolvedConfig) {
      viteConfig = resolvedConfig
    },

    transform(code: string, id: string) {
      if (fileRegex.test(id)) {
        css.push(code)
        return {
          code: '',
        }
      }
      if (
        // @ts-ignore
        id.includes(viteConfig.build.lib.entry)
      ) {
        return {
          code: `${code}
          ${template}`,
        }
      }
      return null
    },

    async writeBundle(_: any, bundle: any) {
      for (const file of Object.entries(bundle)) {
        const { root } = viteConfig
        const outDir: string = viteConfig.build.outDir || 'dist'
        const fileName: string = file[0]
        const filePath: string = resolve(root, outDir, fileName)

        try {
          let data: string = fs.readFileSync(filePath, {
            encoding: 'utf8',
          })

          if (data.includes(template)) {
            data = data.replace(template, injectCode(css.join('\n')))
          }

          fs.writeFileSync(filePath, data)
        } catch (e) {
          console.error(e)
        }
      }
    },
  }
}

https://github.com/ohbug-org/ohbug-extension-feedback/blob/main/libInjectCss.ts

How to use it? install a npm?

xinyao27 commented 3 years ago

I encountered the same problem, css cannot be loaded correctly when building lib mode. I created a plugin to temporarily solve this problem.

import fs from 'fs'
import { resolve } from 'path'
import type { ResolvedConfig, PluginOption } from 'vite'

const fileRegex = /\.(css)$/

const injectCode = (code: string) =>
  `function styleInject(css,ref){if(ref===void 0){ref={}}var insertAt=ref.insertAt;if(!css||typeof document==="undefined"){return}var head=document.head||document.getElementsByTagName("head")[0];var style=document.createElement("style");style.type="text/css";if(insertAt==="top"){if(head.firstChild){head.insertBefore(style,head.firstChild)}else{head.appendChild(style)}}else{head.appendChild(style)}if(style.styleSheet){style.styleSheet.cssText=css}else{style.appendChild(document.createTextNode(css))}};styleInject(\`${code}\`)`
const template = `console.warn("__INJECT__")`

let viteConfig: ResolvedConfig
const css: string[] = []

export default function libInjectCss(): PluginOption {
  return {
    name: 'lib-inject-css',

    apply: 'build',

    configResolved(resolvedConfig: ResolvedConfig) {
      viteConfig = resolvedConfig
    },

    transform(code: string, id: string) {
      if (fileRegex.test(id)) {
        css.push(code)
        return {
          code: '',
        }
      }
      if (
        // @ts-ignore
        id.includes(viteConfig.build.lib.entry)
      ) {
        return {
          code: `${code}
          ${template}`,
        }
      }
      return null
    },

    async writeBundle(_: any, bundle: any) {
      for (const file of Object.entries(bundle)) {
        const { root } = viteConfig
        const outDir: string = viteConfig.build.outDir || 'dist'
        const fileName: string = file[0]
        const filePath: string = resolve(root, outDir, fileName)

        try {
          let data: string = fs.readFileSync(filePath, {
            encoding: 'utf8',
          })

          if (data.includes(template)) {
            data = data.replace(template, injectCode(css.join('\n')))
          }

          fs.writeFileSync(filePath, data)
        } catch (e) {
          console.error(e)
        }
      }
    },
  }
}

https://github.com/ohbug-org/ohbug-extension-feedback/blob/main/libInjectCss.ts

How to use it? install a npm?

// vite.config.ts
import { defineConfig } from 'vite'
import libInjectCss from './libInjectCss'

export default defineConfig({
  plugins: [
    libInjectCss(),
  ],
})

Here is a sample file: https://github.com/ohbug-org/ohbug-extension-feedback/blob/main/vite.config.ts

m-nathani commented 2 years ago

i cant really find a sustainable way to make the styles inline in lib mode... my lib is used in more then 5 place.. and expected to grow.. and i don't want to manually import style file everywhere.. so wanted to include as build itself...

is vite going proposed any solution ? 🤔

koooge commented 2 years ago

Same here. To me as a vue user, vue-cli's extract: false works fine in lib mode. https://cli.vuejs.org/config/#css-extract I am wondering if vite does not have the option on purpose. But any solution would be needed.

ruofee commented 2 years ago

Excuse me, is there a solution to this problem?

ruofee commented 2 years ago

Excuse me, is there a solution to this problem?

I wrote a vite plugin:

https://github.com/ruofee/vue-dynamic-form-component/blob/vite/build/ViteSingleCssPlugin.js

wal0x commented 1 year ago

Both plugins shared above did not work for me... and I am still looking for a solution...

emosheeep commented 1 year ago

Both plugins shared above did not work for me... and I am still looking for a solution...

You can check adjacent 2 comments at https://github.com/vitejs/vite/issues/1579#issuecomment-1482683935

marcelobotega commented 1 year ago

Is this feature still relevant for Vite ? If yes, I can try to develop it.

mrswylet commented 1 year ago

Hi. I'm developing a UI kit. At the moment I use vue-cli and I can't switch to vite because of this problem.

marcelobotega commented 1 year ago

Hey, I created a PR for this #13565. Feel free to look into it.

tibineagu commented 11 months ago

@marcelobotega thank you for building this! It will be very helpful!

Do you know when #13565 might get merged?

marcelobotega commented 11 months ago

Hey @tibineagu, sadly I do not have the time to update the PR right now (could not find a way to make the legacy work with multiple entries), but we ended using this solution on our library and worked better than the cssExtract option. I'll leave the vite.config file here in case anyone want to check it out.

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { visualizer } from 'rollup-plugin-visualizer';
import { PluginPure } from 'rollup-plugin-pure';
import { createRequire } from 'module';
import { libInjectCss } from 'vite-plugin-lib-inject-css';
import { extname, relative } from 'path';
import { fileURLToPath } from 'node:url';
import { glob } from 'glob';

const require = createRequire(import.meta.url);
const pkg = require('./package.json');
const external = Object.keys(pkg.dependencies);

export default defineConfig({
  optimizeDeps: {
    exclude: ['vue-demi'],
  },
  plugins: [
    vue(),
    libInjectCss(),
    PluginPure({
      functions: ['defineComponent'],
      sourcemap: true,
    }),
    {
      ...visualizer({ template: 'sunburst', sourcemap: true }),
      apply: 'build',
    },
  ],
  outDir: './dist',
  build: {
    cssCodeSplit: true,
    sourcemap: true,
    lib: {
      entry: ['main.js'],
      formats: ['es'],
    },
    rollupOptions: {
      // https://rollupjs.org/configuration-options/#input
      input: Object.fromEntries(
        glob
          .sync('src/**/*.{js,ts,vue}', {
            ignore: {
              ignored: p => /\.spec.(js|ts)$/.test(p.name),
            },
          })
          .map(file => [
            relative('src', file.slice(0, file.length - extname(file).length)),
            fileURLToPath(new URL(file, import.meta.url)),
          ])
      ),
      output: {
        assetFileNames: '[name][extname]',
        entryFileNames: '[name].mjs',
      },
      external: [...external.filter(dependency => !dependency.includes('vue-demi')), 'vue'],
    },
  },
});
marcelobotega commented 11 months ago

And shout out to @emosheeep for making the plugin, it works like a charm together with multiple entries option from rollup.

emosheeep commented 11 months ago

And shout out to @emosheeep for making the plugin, it works like a charm together with multiple entries option from rollup.

Thanks so much,I still remember the day in last year when I was developing on my vue component library with multi entries in vite. I was driven into a corner, so I had to dive into the source code and found something useful. It's really a simple plugin that only contains 100 lines, I'm glad to see it helping so many guys🎉🎉🎉.