I am creating an app in Svelte and I have a problem with styles.
I preprocess imports in style tags using the rollup.config.js file, not with svelte-preprocess, but with rollup-plugin-svelte preprocess.
I do it following the example of the official docs. https://svelte.dev/docs#svelte_preprocess
Everything works fine, in the sass return code: css.toString(); I get the css code from my imports, but the result is not added to my bundle.css, it just disappears.
What am I missing?
My rolling.config.js is:
...
...
plugins: [
svelte({
dev: !production,
css: css => {
css.write('public/build/bundle.css');
},
preprocess: {
style: async ({ content, attributes, filename }) => {
// only process <style lang="sass">
if (attributes.lang !== 'sass') return;
const { css, stats } = await new Promise((resolve, reject) => sass.render({
file: filename,
data: content,
includePaths: [
path.dirname(filename),
'./node_modules',
],
}, (err, result) => {
if (err) reject(err);
else resolve(result);
}));
return {
code: css.toString(), // this works
dependencies: stats.includedFiles
};
}
},
}),
resolve({
browser: true,
dedupe: ['svelte']
}),
...
...
In one of my .svelte files
<style lang="sass">
#import './styles/App.scss';
</style>
All other styles without the lang = "sass" attribute are not preprocessed and are added to the bundle.css file.
I'm blocked, does anyone help me?
Add emitCss: true in svelte(..) like this :
svelte({
dev: !production,
emitCss: true, // without this, <style> in components are not included in bundle
css: css => {
css.write('public/build/bundle.css')
}
}),
This will emit CSS as "files" for other plugins to process.
Use the default svelte template that renders css to add css support appropriately... For further details check https://github.com/sveltejs/template/blob/master/rollup.config.js
set bundle.css as indicated
Related
In Nuxt 2 I could use server-side rendered Stencil components by leveraging the renderToString() method provided in the Stencil package in combination with a Nuxt hook, like this:
import { renderToString } from '[my-components]/dist-hydrate'
export default function () {
this.nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: false
})
page.html = render.html
})
}
Since the recent release of Stencil 2.16.0 I'm able to use native web components in Nuxt 3 that is powered by Vite. However I haven't found a way to hook into the template hydration process. Unfortunately there is no documentation for the composable useHydration() yet.
Does anybody know how I could get this to work in Nuxt 3?
I had the same problem. I solved it via a module.
Make a new custom nuxt module. documentation for creating a module
In the setup method hook into the generate:page hook:
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
documentation for nuxt hooks
documentation for stencil hydration (renderToString)
Register the css classes you need via nuxt.options.css.push(PATH_TO_CSS)
Register the module in the nuxt config.
Note: Make sure in the nuxt.config.ts the defineNuxtConfig gets exported as default.
Tap the vue compiler options in the nuxt config:
vue: {
compilerOptions: {
isCustomElement: (tag) => TEST_TAG_HERE,
},
},
This depends on how you wan't to use the custom elements. In my case I defined the elements over the stencil loader in my app.vue file:
import { defineCustomElements } from '<package>/<path_to_loader>';
defineCustomElements();
You could also import the elements you need in your component and then define them right there, for example in a example.vue component:
import { CustomElement } from '<package>/custom-elements';
customElements.define('custom-element', CustomElement);
Here is an example from my module and config:
./modules/sdx.ts
import { defineNuxtModule } from '#nuxt/kit';
import { renderToString } from '#swisscom/sdx/hydrate';
export default defineNuxtModule({
meta: {
name: '#nuxt/sdx',
configKey: 'sdx',
},
setup(options, nuxt) {
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
nuxt.options.css.push('#swisscom/sdx/dist/css/webcomponents.css');
nuxt.options.css.push('#swisscom/sdx/dist/css/sdx.css');
},
});
Important: This only works if the stenciljs package supports hydration or in other words has a hydrate output. Read more here
./nuxt.config.ts
import { defineNuxtConfig } from 'nuxt';
//v3.nuxtjs.org/api/configuration/nuxt.config export default
export default defineNuxtConfig({
typescript: { shim: false },
vue: {
compilerOptions: {
isCustomElement: (tag) => /sdx-.+/.test(tag),
},
},
modules: ['./modules/sdx'],
});
./app.vue
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<script setup lang="ts">
import { defineCustomElements } from '#swisscom/sdx/dist/js/webcomponents/loader';
defineCustomElements();
// https://v3.nuxtjs.org/guide/features/head-management/
useHead({
title: 'demo',
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
charset: 'utf-8',
meta: [{ name: 'description', content: 'demo for using a stencil package in a nuxt ssr app' }],
bodyAttrs: {
class: 'sdx',
},
});
</script>
Update
I tested my setup with multiple components and it looks like you cannot define your components in the module. I updated the answer to my working solution.
I've found defining a plugin using the 'render:response' hook to work for me:
server/plugins/ssr-components.plugin.ts
import { renderToString } from '#my-lib/components/hydrate';
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('render:response', async (response) => {
response.body = (await renderToString(response.body)).html;
});
});
Perhaps it will work for you :)
Try this in defineNuxtPlugin
nuxtApp.hook('app:rendered', () => {
const response = nuxtApp.ssrContext?.res
if (!response)
return
const end = response.end
response.end = function(chunk) {
chunk = 'hijacked'
end(chunk)
}
})
I am working on a project which is set up with lerna mono repo, we have multiple stencilJS projects for an individual component inside packages of monorepo.
My project sructure is:
I am new to the storybook, now I have to set up the storybook at the root level which all the packages storybook.
I followed an article on the internet and I have set up something which works only for a single package component, due to the current style of setup.
Due to defineCUstomElements in preview.js it is loading the first project package loader I am able to see only the first project stories. Css is not loading for second project stories.
Can someone help me to set up a storybook at the root level which works for all packages?
My example
storybook/main.js
module.exports = {
"stories": [
"../stories/**/*.stories.mdx",
"../packages/plugin-*/src/components/plugin-*/*.stories.#(js|jsx|ts|tsx)"
],
"addons": [
'#storybook/addon-links',
'#storybook/addon-essentials',
'#storybook/addon-viewport',
'#storybook/addon-notes',
'#storybook/addon-docs'
]
}
storybook/preview.js
import { defineCustomElements } from '../packages/stencilProj1/loader';;
defineCustomElements();
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
};
package/stencilProj1/component1.stories.ts
import readme from './readme.md'
import React from 'react';
import ComponentButton from '../../../dist/collection/components/ComponentButton /ComponentButton';
export default {
title: 'Button',
component: ComponentButton,
argTypes: {
label: { control: 'text' },
type: { type: 'select', options: ['primary'] },
disabled: { control: 'boolean' }
},
parameters: {
markdown: readme
},
};
const Template = ({ label, type, disabled = false }) => {
return <component-button type={type} disabled={disabled}>{label}</component-button>;
};
export const Primary = Template.bind({});
Primary.args = {
type: 'primary',
label: 'Primary Button',
};
After a couple of months of attempts, I finally solved this puzzle :) And want to share my solution with community
I have a lerna v4 monorepo for react v17 + mui v5 components written in typescript and flavored with storybook v6 and webpack v5
mui has its wrappers in preview.js, that's why I added the path to .storybook in babel-loader
module.exports = {
core: {
builder: "webpack5",
},
framework: "#storybook/react",
stories: ["../components/**/*.stories.#(ts|tsx)"],
addons: [
"#storybook/addon-links",
"#storybook/addon-essentials",
{
name: "#storybook/preset-create-react-app",
options: {
craOverrides: {
fileLoaderExcludes: ["less"],
},
},
},
],
webpackFinal: config => {
const {
module: {
rules: [, , , , , { oneOf }],
},
} = config;
const babelLoader = oneOf.find(({ test }) => new RegExp(test).test(".ts"));
babelLoader.include = [/components\/(.*)\/src/, /.storybook/];
babelLoader.options.sourceType = "unambiguous";
return config;
},
};
it is also worth to mention my tsconfig has these lines
"rootDirs": [
"./src",
],
Have you tried to import project 2's defineCustomElement with "as" to rename it and use it?
(something along the line of following inside preview.js)
import { defineCustomElements } from '../packages/stencilProj1/loader';
import { defineCustomElements as defineSecondProject } from '../packages/stencilProj2/loader';
defineCustomElements();
defineSecondProject();
This is very manual and even if this works, if you have many repo might not be good solution but I've done something similar to load multiple component loaders and that seem to work OK for my use case in the past.
I have an MPA app, where vue.js is used as a part of the application. I have a very simple test set up, here:
relevant parts of my template
....
<div id='app-basket-checkout'>
<h1>Better Be Here</h1>
</div>
....
pageBasketCheckout.js (essentially my app.js)
import Vue from 'vue'
import AppBasketCheckout from './BasketCheckout.vue'
import './dummyScss.css'
Vue.config.productionTip = false
new Vue({
render: h => h(AppBasketCheckout)
}).$mount('#app-basket-checkout')
component
<template>
<div id="app-basket-checkout">
{{msg}}
</div>
</template>
<script>
export default {
name: 'AppBasketCheckout',
components: {
},
data() {
return {
msg: 'Hello'
}
}
}
</script>
<style scoped>
</style>
So the above code renders just fine in my front end. I end up with an extra div that has hello printed inside, well done.
However when I add css to the style tag:
<template>
<div id="app-basket-checkout">
{{msg}}
</div>
</template>
<script>
export default {
name: 'AppBasketCheckout',
components: {
},
data() {
return {
msg: 'Hello'
}
}
}
</script>
<style scoped>
body {
font-family: Arial, Helvetica, sans-serif;
line-height: 1.4;
}
</style>
This produces this error in chrome:
Uncaught Error: Cannot find module './BasketCheckout.vue?vue&type=style&index=0&id=2711cf65&scoped=true&lang=css&'
at webpackMissingModule (VM45512 BasketCheckout.vue:4)
at eval (VM45512 BasketCheckout.vue:4)
at Module../src/BasketCheckout.vue (pageBasketCheckout.bundle.js:40)
at __webpack_require__ (index.bundle.js:4312)
at eval (pageBasketCheckout.js:3)
at Module../src/pageBasketCheckout.js (pageBasketCheckout.bundle.js:29)
at __webpack_require__ (index.bundle.js:4312)
at checkDeferredModulesImpl (index.bundle.js:4453)
at webpackJsonpCallback (index.bundle.js:4435)
at pageBasketCheckout.bundle.js:9
Again this error only happens when adding css to the component. Here is my webpack.config.js:
const path = require('path');
const webpack = require('webpack')
const glob = require('glob')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
watch: true,
context: path.resolve(__dirname, 'uniquesite/uniquesite'),
mode: 'development',
entry: {
index: {
import: ['#babel/polyfill', './src/index.js'],
// dependOn: ['babel'],
},
pageProductDetails: {
import: ['#babel/polyfill', './src/pageProductDetails.js'],
dependOn: ['index'],
},
pageBasketCheckout: {
import: ['#babel/polyfill', './src/dummyScss.scss', './src/pageBasketCheckout.js'],
dependOn: ['index']
}
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'uniquesite/uniquesite/static/uniquesite/js/'),
},
plugins: [
new VueLoaderPlugin()
],
resolve: {
alias: {
jquery: "jquery/src/jquery",
'jquery-ui': "jquery-ui-dist/jquery-ui.js",
boostrap: "bootstrap/dist/js/bootstrap.bundle.js"
}
},
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
},{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
},
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
}
]
}
};
You'll note I've also tried importing a dummy .css file to ensure the style loader works, as I've seen one more SO question with a similar problem that solved it that way. That didn't work for me however.
Update 1
My current thinking is that the problem has to be happening in the VueLoaderPlugin. That plugin is reponsible for splitting the script into distinct parts for template, logic, and style. It looks like the style is not actually making it into the bundle. See below.
"use strict";
eval(
"__webpack_require__.r(__webpack_exports__);
/* harmony import */
var _BasketCheckout_vue_vue_type_template_id_2711cf65___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
/*! ./BasketCheckout.vue?vue&type=template&id=2711cf65& */
\"./src/BasketCheckout.vue?vue&type=template&id=2711cf65&\"
);
/* harmony import */
var _BasketCheckout_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
/*! ./BasketCheckout.vue?vue&type=script&lang=js& */
\"./src/BasketCheckout.vue?vue&type=script&lang=js&\"
);
Object(function webpackMissingModule() {
var e = new Error(
\"Cannot find module './BasketCheckout.vue?vue&type=style&index=0&lang=css&'\"
); e.code = 'MODULE_NOT_FOUND';
throw e;
}());
/* harmony import */
var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(
/*! !../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */
\"../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"
);
/* normalize component */
var component = (
0,_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__.default
)(
_BasketCheckout_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__.default,
_BasketCheckout_vue_vue_type_template_id_2711cf65___WEBPACK_IMPORTED_MODULE_0__.render,
_BasketCheckout_vue_vue_type_template_id_2711cf65___WEBPACK_IMPORTED_MODULE_0__.staticRenderFns,
false,
null,
null,
null)
/* hot reload */
if (false) { var api; }
component.options.__file = \"src/BasketCheckout.vue\"
/* harmony default export */
__webpack_exports__[\"default\"] = (component.exports);
//# sourceURL=webpack:///./src/BasketCheckout.vue?"
);
Scoped CSS rules only apply to the current component (and its child components' root nodes).
You are mounting your Vue instance at #app-basket-checkout, which is already inside a <body> element.
You can style <body>, but do it using a global stylesheet that is imported in your app.js, not a subcomponent.
Alternatively, you can apply a class-based style at a low level node within your Vue instance and likely deliver your desired styles.
I have a few SCSS theme files I want to extract to CSS files and later load them into the page. I want to be able to use contenthash for long term caching.
Since I'm using Webpack 4, I am also using mini-css-extract-plugin. I started down the path of creating a splitChunks in my webpack config.
// webpack.config.js
module.exports = {
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "[name].[contenthash].css",
chunkFilename: "[id].[contenthash].css"
})
],
optimization: {
splitChunks: {
cacheGroups: {
'vendor': {
// custom commons chunk for js
},
'theme-a': {
test: /theme-a.\scss/,
},
'theme-b': {
test: /theme-b.\scss/,
},
// more themes
}
}
}
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}
]
}
}
I've then tried dynamically importing the css in my app:
// app.js
class App extends React.Component {
// constructor
login(themeName) {
import(/* webpackChunkName: "`${themeName}`" */ `./path/to/${themeName}.scss`).then(theme => {
// do something with `theme`
}
}
// other stuff
}
I need to be able to load that css file dynamically in login() and I'm just not sure how to reference it when it has a generated [contenthash].
TLDR: Is there a good way to both extract css and import the referenced CSS bundle to lazy load later? I'm not tied to mini-css-extract-plugin.
Edit: Created a mini-css-extract-plugin issue here.
My solution ended up using extract-text-webpack-plugin. My config now looks like this:
// webpack.config.js
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ExtractThemeA = new ExtractTextPlugin({ filename: 'themeA.[hash].css', allChunks: true});
module.exports = {
plugins: [
ExtractThemeA,
// more plugins for other css files
],
optimization: {
splitChunks: {
cacheGroups: {
// Note: No changes to splitChunks
'vendor': {
// custom commons chunk for js
}
}
}
module: {
rules: [
{
test: /theme-a\.scss$/,
use: ExtractThemeA.extract([ 'css-loader', 'sass-loader' ])
},
// more module rules for each css file needed
]
}
}
Then, these chunks are available by file name in my HtmlWebpackPlugin:
<!-- HtmlWebpackPlugin Template -->
<script>
// provides me with an array of file name strings
var themesManifest = <%= htmlWebpackPlugin.files.css %>
</script>
Sorry for my miss understanding,
You could probably just make two different scss-files and import them as needed. theme.scss admin.scss or like so
This is how I am doing scss in React right now
In App.js
import styles from '../../stylesheet/main.scss'
// could be as well
import styles1 from '../../stylesheet/theme.scss' // some file
import styles2 from '../../stylesheet/admin.scss' // some other file
const App = () => {
<div className={styles.action_feed} >{ content }</div>
}
In main.scss
.action_feed {
position: fixed;
width: 350px;
height: auto;
max-height: 100%;
max-width: 100%;
top: 0;
left: 0;
z-index: 9999;
}
I think you could just as well do it like so
const themeName = 'main'
import(`./stylesheet/${themeName}.scss`, (css) => {
// meaby set this to state? not quite sure how should properly handle
// dynamically imported css
this.setState({ css: css.action_feed })
// or possible this way. I have done some non-React dom manipulation
// this way before
document.querySelector('body').classList.add(css.action_feed)
})
<div className={this.state.css}>{ content }</div>
You should probably check out React's new Refs API as well. It might give you some nice flexibility for giving className-attr to required element.
Having set to splitChunks.chunks to all works though i think in this case anyway
I'm running my unit tests and seeing the actual application page below the results. It'd be nice if it had the styling applied to it, as the images etc go massive and it's not worth looking at.
Here is my setup code:
describe('ResultImageComponent', () => {
let fixture: ComponentFixture<UserAttributesCardComponent>;
let component: UserAttributesCardComponent;
let element: HTMLElement;
let result: Result;
let page: Page;
beforeAll(() => {
TestBed.resetTestEnvironment();
TestBed.initTestEnvironment(BrowserDynamicTestingModule,
platformBrowserDynamicTesting());
});
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [UserAttributesCardComponent],
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(UserAttributesCardComponent);
component = fixture.componentInstance;
createComponent();
}));
function createComponent() {
page = new Page();
component.user = new UserService().getBen();
fixture.detectChanges();
return fixture.whenStable().then(() => {
fixture.detectChanges();
page.addPageElements();
});
}
The component under test has an external template which is being loaded fine but it also has an external style sheet which isn't loading.
Can this be done while configuring the test bed?
In the karma.conf.js file you can add an array of files to the config object.
files: [
"../path/to/file.css"
],