-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can css be styleInjected in library mode? #1579
Comments
The problem is injecting the style assumes a DOM environment which will make the library SSR-incompatible. If you only have minimal css, the easiest way is to simply use inline styles. |
Generally, existing style libraries are used in a dom environment. |
I know you already answered that assuming a DOM environment will make the library SSR-incompatible, but maybe it would be possible to provide an option for enabling inlining of the styles? I have made a reproduction project showing a component being created in the ui-components project then after being built and installed in the separate project not including any styles. vite-library-style-repro @yyx990803 how would you suggest making a library of components that can be included without importing a global styles.css, possibly including styles that are unneeded or clashing with other styles? |
In pass, I use rollup build vue2 library, I found that the style will auto inject to "<head></head>" |
I've been running into this for the past week and pulling my hair out, as this is not a documented behavior. I was about to file an issue (repro: https://github.com/richardtallent/vite-bug-cssnotimported) and just came across this one. Requiring non-SSR users to manually import a CSS file to get a component's default style to work is suboptimal DX, and an unfamiliar surprise for anyone accustomed to importing components under Vue 2. Inline CSS is not a viable alternative, at least for a non-trivial component. Inline CSS also creates additional challenges for a user who wants to override the default style. Is there a middle ground here to help developers who want to support the SSR use case, without making non-SSR users jump through an extra hoop to style each component they use? |
@richardtallent Sharing my experience here, I moved to inline all CSS and moved all I was following this issue for over 2 weeks now, I was stumbled upon how to effectively inject style into the head. Effectively for DX, requiring the user to import style was suboptimal for my use case. By comparison Vue 2 was much easier to install for the users than Vue 3, so this was a no go for me. My trivial library target SSR/SSG/SPA and is e2e tested on all 3 modes which makes it an extra headache to move to Vue 3 in Vite. There was also some quirks with head injected css for my use case so inlining style was not something I was entirely against. (In fact I wanted to for consistency sake.) For inline CSS overriding, what I did to allow default style override is to explicitly state how to with .some-component ::v-deep(.v-hl-btn) {
width: 8px !important;
} Not all css can be inlined directly (pseduo, media query, '>' and many more), which require me to rewrite my entire component into render function. reference export default defineComponent({
render() {
return h('div', {
class: 'vue-horizontal',
style: {
position: 'relative',
display: 'flex',
}
}, [...])
}
}) I hope this helps anyone down this road. For reference: fuxingloh/vue-horizontal#87 |
Thanks @fuxingloh! This sounds like an interesting workaround, but I don't want to give up on writing idiomatic SFCs (with template and style blocks) and allowing users to override styles with standard CSS. However, I have bookmarked your component since I like how you've done your testing and I hope to learn from it! |
I am using the import { computed, defineComponent, ref } from 'vue';
import { queryMedia } from '@convue-lib/utils';
import styleInject from 'style-inject';
import css from './index.less';
styleInject(css);
export default defineComponent({
name: 'Container',
props: {
fuild: {
type: Boolean,
default: false,
},
},
setup(props, { slots }) {
const media = ref('');
queryMedia((data: string) => (media.value = data));
const className = computed(() => ({
'convue-container': true,
fuild: props.fuild,
[media.value]: true,
}));
return () => <div class={className.value}>{slots.default?.()}</div>;
},
}); https://github.com/ziping-li/convue-lib/blob/master/packages/container/src/index.tsx |
Inlining style won't work with the transition also. Requiring users to explicitly import the style is not really an option. Is there anything we can do to settle down this problem? |
This comment was marked as spam.
This comment was marked as spam.
@ziping-li @hiendv @aekasitt // index.ts entry point
import styleInject from 'style-inject'
styleInject('__STYLE_CSS__') // __STYLE_CSS__ is a placeholder pseudo code // gulpfile.js build script file
const { readFileSync, rmSync, writeFileSync } = require('fs')
const { series } = require('gulp')
const { exec } = require('shelljs')
function readWriteFileSync(path, callback) {
writeFileSync(path, callback(readFileSync(path, { encoding: 'utf8' })), { encoding: 'utf8' })
}
function readRemoveFileSync(path, callback) {
callback(readFileSync(path, { encoding: 'utf8' }))
rmSync(path, { force: true })
}
async function clean() {
exec('rimraf ./dist ./coverage ./.eslintcache ./.stylelintcache')
}
async function build_library() {
exec('tsc')
exec('copyfiles -u 1 ./src/**/* ./dist') // copy style to tsc dist
exec('vite build')
readRemoveFileSync('./dist/style.css', css => {
// __STYLE_CSS__ replace by style.css
readWriteFileSync('./dist/library.umd.js', js => replace(js, '__STYLE_CSS__', css))
})
}
exports.build = series(clean, build_library) // package.json
{
"scripts": {
"build": "gulp build"
}
} |
This comment was marked as spam.
This comment was marked as spam.
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 |
@chenyueban This completely solved my problem, thanks! Much more elegant than crawling the dependency graph 👍 |
@chenyueban Hi, friend. I use your plugin in my lib code, and it doesn't work. And I found difference is that I turned on |
Is there any other way, please? I see that rollup can directly output an esm file and include css. |
I faced the same issue. I wrote a Vue composition function that injects scoped CSS from a string at runtime that uses the same approach as Kremling for React. It is not a perfect solution, since I imagine the majority of Vue users would prefer to use the standard SFC |
Sorry, that was Kami (my cat) |
What if we just provide a option to import the css file into the bundled js file like this, instead of inject into DOM, will this be more SSR friendly? // bundled js file (in es format only)
import './style.css';
import { defineComponent, openBlock, createBlock, renderSlot, withScopeId } from "vue";
// ... And the css import will be handled by the downstream bundler like, webpack, rollup, vite, whatever. |
the id and the entry was different in windows. |
For anyone interested, this is the solution I ended up with and it works fine for my purpose: Assuming all your consumers have a bundler setup that can handle CSS, you can use vite-plugin-lib-inject-css to add import statements for the generated CSS to the output JavaScript files. Importing the CSS rather than injecting it into the DOM has have the advantage that the consumers can further process the CSS styles (such as doing optimizations eg. grouping selectors). This solution is great for scenarios where your library uses CSS modules but not all consumers do (or consumers have a different style of importing CSS modules). To make sure not all CSS styles end up in your application, you can split up the bundle. In the Rollup documentation there is a recommended way of doing this:
You can find a demo repo here: https://github.com/receter/my-component-library This is my vite configuration:
// vite.config.ts
import { defineConfig } from 'vite'
import { extname, relative, resolve } from 'path'
import { fileURLToPath } from 'node:url'
import { glob } from 'glob'
import react from '@vitejs/plugin-react'
import dts from 'vite-plugin-dts'
import { libInjectCss } from 'vite-plugin-lib-inject-css'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
libInjectCss(),
dts({ include: ['lib'] })
],
build: {
copyPublicDir: false,
lib: {
entry: resolve(__dirname, 'lib/main.ts'),
formats: ['es']
},
rollupOptions: {
external: ['react', 'react/jsx-runtime'],
input: Object.fromEntries(
// https://rollupjs.org/configuration-options/#input
glob.sync('lib/**/*.{ts,tsx}').map(file => [
// 1. The name of the entry point
// lib/nested/foo.js becomes nested/foo
relative(
'lib',
file.slice(0, file.length - extname(file).length)
),
// 2. The absolute path to the entry file
// lib/nested/foo.ts becomes /project/lib/nested/foo.ts
fileURLToPath(new URL(file, import.meta.url))
])
),
output: {
assetFileNames: 'assets/[name][extname]',
entryFileNames: '[name].js',
}
}
}
}) |
If you only have minimal css, The easiest way to do use
|
hahahaha,can't believe it could be done in this way |
You should probably also add ./style.css to the sideEffects field in your package.json |
Without this, when building, Vite would generate a style.css file that would need to be imported by users in the host app, which is not an ideal DX See vitejs/vite#1579
When building components using rollup in our project, we used https://www.npmjs.com/package/rollup-plugin-postcss - this automatically inject styles from Vue SFC into the head of the page (per component). Unfortunately, the above plugin does not work with Vite. We have several components that load asynchronously, the styles of these components should also load asynchronously. |
Re: Using There's a lot of references to using |
I am using vite with Vue to build a component library with multiple entry points (to enable imports like With this setup, vite / rollup creates folders in the
import { defineConfig } from 'vite';
import { resolve } from 'path';
import vue from '@vitejs/plugin-vue';
import path from 'path';
import fs from 'fs';
import { promisify } from 'util';
const writeFile = promisify(fs.writeFile);
const readFile = promisify(fs.readFile);
const exists = promisify(fs.exists);
// in my actual code, this is pulled out into a helpers/ directory
async function addCSSImport() {
return {
name: 'add-css-import',
async writeBundle(options, bundle) {
const outputDir = options.dir || '.';
for (const [fileName] of Object.entries(bundle)) {
if (fileName.endsWith('.vue.js')) {
// Extract the base file name without the directory path
const baseFileName = path.basename(fileName, '.vue.js');
// Construct the full path to the JS and CSS files
const jsFilePath = path.join(outputDir, fileName);
const cssFilePath = jsFilePath.replace('.vue.js', '.css');
// Check if the CSS file exists
if (await exists(cssFilePath)) {
// Read the existing JS file
let jsFileContent = await readFile(jsFilePath, 'utf8');
// Generate the import statement for the CSS file
const cssImport = `import './${baseFileName}.css';\n`;
// Prepend the import statement to the JS file's content
jsFileContent = cssImport + jsFileContent;
// Write the modified JS file content back to the file
await writeFile(jsFilePath, jsFileContent, 'utf8');
}
}
}
}
};
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
build: {
// prevent monolithic css file from being generated, preferring one for each component
cssCodeSplit: true,
lib: {
entry: {
['global-styles']: 'src/styles/app.scss',
['buttons.index']: 'src/components/buttons/index.js',
['test.index']: 'src/components/test/index.js'
},
name: 'my-package-name',
formats: ['es'] // only export ES module build
},
rollupOptions: {
// externalize deps that shouldn't be bundled into the library
external: ['vue'],
output: {
globals: {
vue: 'Vue'
},
// Preserve the modules and directories in the final build
preserveModules: true
},
plugins: [addCSSImport()]
}
}
}); and in my
|
Time to let grampa Webpack retire. * [x] vite build working * [x] remove webpack & babel * [x] react to `17.0.2` (matches Bloom Editor & needed for modern storybook) * [x] switch everything to modern es modules * [x] get dynamic loading of activities working * [x] check svg icons in controlBar.tsx. * [x] check that iso639-autonyms.tsv is loading/working * [x] in ActivityContext, make sure the sounds still work * [x] Get simple comprehension quizzes working (already reported ### `yarn dev` experience * [x] vite dev server working (hot reloading, server, etc) * [x] use `s3/` with proxy server in place of `https://bloomlibrary.org` to make CORS work * [x] serve books from `testBooks/` ### Output / Packaging * [x] bloomPlayer.js building with a hash and bloomplayer.htm containing a script tag * [x] check for any significant increase in bundle size <span style="color:darkgreen;">(was 1.1MB, now 800kb)</span> * [x] vite *really* doesn't want to inline css (it interferes with SSR somehow). vitejs/vite#1579. I tried <https://github.com/wxsms/vite-plugin-libcss> to no effect, but it says it's for lib mode only which we don't want. In the end I just linked to the css from the bloomPlayer.htm, problem solved. * [x] move crowdin stuff out of build system and into npm scripts. ### Unit Tests * [x] jest --\> vitest ### Storybook * [x] storybook upgraded and working with vite * [x] migrate the "big set of books" story in `index.tsx`
Time to let grampa Webpack retire. * [x] vite build working * [x] remove webpack & babel * [x] react to `17.0.2` (matches Bloom Editor & needed for modern storybook) * [x] switch everything to modern es modules * [x] get dynamic loading of activities working * [x] check svg icons in controlBar.tsx. * [x] check that iso639-autonyms.tsv is loading/working * [x] in ActivityContext, make sure the sounds still work * [x] Get simple comprehension quizzes working (already reported ### `yarn dev` experience * [x] vite dev server working (hot reloading, server, etc) * [x] use `s3/` with proxy server in place of `https://bloomlibrary.org` to make CORS work * [x] serve books from `testBooks/` ### Output / Packaging * [x] bloomPlayer.js building with a hash and bloomplayer.htm containing a script tag * [x] check for any significant increase in bundle size <span style="color:darkgreen;">(was 1.1MB, now 800kb)</span> * [x] vite *really* doesn't want to inline css (it interferes with SSR somehow). vitejs/vite#1579. I tried <https://github.com/wxsms/vite-plugin-libcss> to no effect, but it says it's for lib mode only which we don't want. In the end I just linked to the css from the bloomPlayer.htm, problem solved. * [x] move crowdin stuff out of build system and into npm scripts. ### Unit Tests * [x] jest --\> vitest ### Storybook * [x] storybook upgraded and working with vite * [x] migrate the "big set of books" story in `index.tsx`
Sorry to bother you, I'm a beginner and I'm trying to build my own component library! But I encountered a similar problem, I'm not sure if it fits this issue, if you know how to solve it or it belongs to this issue, please tell me! My question: |
You should add css files into the |
Thank you very much for replying to me! I searched for related content before trying to add sideEffects: I did not find the sideEffects field in npm doc and vite doc, but I saw some webpack related ones! Can you tell me where I can get the knowledge related to sideEffects? I set My repo is here:https://github.com/Ys-OoO/vue-sketch-ui .You can see the detailed configuration here |
I seem to have solved the problem. I didn't use sideEffects. The global styles I introduced in the entry file and the styles of each component are packaged into a css file. This way, users can import them when they use them! But I still want to know about |
https://github.com/vitejs/vite/blob/main/packages/vite/src/node/packages.ts#L177 |
for those who are exporting multiple files and only want to add output: {
intro: (chunk) => {
if (chunk.fileName === 'index.js') {
return `import "./style.css";`
}
return ''
},
} https://rollupjs.org/configuration-options/#output-intro-output-outro Adding a intro is a clean and easy solution more than adding a plugin, if it works for you. |
Is your feature request related to a problem? Please describe.
I'm using vite to build a library, and sometimes the library requires a dozen lines of simple styles. After building, a style.css file will be generated. But I think it is not necessary to generate two files, it may be enough to generate only one file. Is there any way to add style.css to the target js file?
The styleInject helper function is used in rollup and babel plug-ins. In iife/umd mode, the style is automatically injected when library loaded.
The text was updated successfully, but these errors were encountered: