How to create my own component library based on Vuetify - vue.js

I want create my component library based on Vuetify and publish on npm.
Vuetify has already vue-plugin standard installation and use vuetify-loader, I think was a more complex scenario than plain HTML component.
For example, I want create my Login Form, my Article Page, my default calendar picker with preset values.
There's a standard way or guide to do this or a sample to do that?
I use last version of vuetify (2.0.7)

I just got it working for Vue3/Vuetify3. In a nutshell (using pnpm, vite, typescript, Vue plugin):
Create the component as a new project:
pnpm create vue#latest
-> your-plugin
-> Typescript
-> ESLint
cd <project>
echo auto-install-peers = true > .npmrc
pnpm add -D vuetify rollup-plugin-typescript2
Then remove all the components and make your own component instead.
Create src\components\index.ts and src\YourPlugin.ts
src\components\index.ts
export {default as YourComponent} from "./YourComponent.vue"
src\YourPlugin.ts
import type { App } from "vue"
import { YourComponent } from "./components"
export default {
install: (app: App) => {
app.component("YourComponent", YourComponent)
}
};
vite.config.ts
Add to the imports:
import vuetify from 'vite-plugin-vuetify'
import typeScript2 from "rollup-plugin-typescript2"
Add to the plugins:
vuetify({
autoImport: true,
}),
typeScript2({
check: false,
include: ["src/components/*.vue"],
tsconfigOverride: {
compilerOptions: {
sourceMap: true,
declaration: true,
declarationMap: true,
}
},
exclude: [
"vite.config.ts"
]
})
Add a new section build to the defineConfig:
build: {
cssCodeSplit: false,
lib: {
entry: "./src/YourPlugin.ts",
formats: ["es", "cjs"],
name: "CommonVtfyPlugin",
fileName: format => (format == "es" ? "index.js" : "index.cjs"),
},
rollupOptions: {
external: ["vue", "vuetify/lib"],
output: {
globals: {
vue: "Vue",
vuetify: "Vuetify",
'vuetify/components': 'VuetifyComponents',
'vuetify/directives': 'VuetifyDirectives'
}
}
}
},
dist\index.d.ts
I have not figured out how to generate this on yet. But this is a generic stand in:
declare const _default: any;
export default _default;
package.json
Add this:
"type": "module",
"exports": {
".": "./dist/index.js"
},
"types": "./dist/index.d.ts",
"files": [
"dist"
],
You can use it in any Vue project by importing it as a plugin:
import YourComponent from 'your-plugin'
app.use(YourComponent)
No guarantees on how optimized that is (feedback welcome).. but it works (tm)..
A more detailed answer can be found pnpm monorepo: how to set up a simple reusable (vue) component for reuse? (any updates will primarily be updated in that answer as well, if any)

Related

ESM library generated with rollup-plugin-postcss throws Cannot find module '../node_modules/style-inject/dist/style-inject.es.js'

We are maintaining an internal library which is exporting ESM modules using Rollup. We have just recently switched to using CSS modules, which we have set with rollup-plugin-postcss. We want to inject these styles into the head rather than have an external file.
Our built bundle generates the ESM file with:
import styleInject from '../node_modules/style-inject/dist/style-inject.es.js';
Our consuming library then fails with
Uncaught Error: Cannot find module '../node_modules/style-inject/dist/style-inject.es.js'
I would expect the ESM export to import styleInject from 'style-inject' and style-inject to be included in the package-lock.json as a dependency. What is the correct way of using CSS Modules and injecting into the head for the consumer of a library?
rollup.config.js
import resolve from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import babel from '#rollup/plugin-babel';
import json from '#rollup/plugin-json';
import postcss from 'rollup-plugin-postcss';
import pkg from './package.json';
import fg from 'fast-glob';
import path from 'path';
export default [
{
input: 'src/index.js',
external: external(),
output: [
{
name: '#my/packageName',
file: pkg.module,
format: 'es',
sourcemap: true,
},
],
plugins: [
{
name: 'watch-external',
async buildStart() {
const files = await fg(['src/index.d.ts', 'playground/**/*']);
for (let file of files) {
this.addWatchFile(path.resolve(file));
}
},
},
json(),
postcss({
modules: true,
}),
babel({
exclude: /node_modules/,
babelHelpers: 'runtime',
babelrc: false,
presets: [
[
'#babel/preset-env',
{
modules: false,
useBuiltIns: 'entry',
corejs: 3,
targets: {
ie: 11,
},
},
],
'#babel/preset-react',
],
plugins: [
'#babel/plugin-transform-runtime',
'#babel/plugin-proposal-class-properties',
'#babel/plugin-proposal-export-namespace-from',
],
}),
commonjs(),
],
},
];
function external() {
const { dependencies = {}, peerDependencies = {} } = pkg;
const externals = [
...Object.keys(dependencies),
...Object.keys(peerDependencies),
];
return id =>
// match 'lodash' and 'lodash/fp/isEqual' for example
externals.some(dep => id === dep || id.startsWith(`${dep}/`));
}
There is a bug in the library where the import is relative rather than being the module name.
There is an open pr, but the library looks like it is no longer being maintained. There are two recommended solutions in the comments:
String replace the built output
Add a custom rollup plugin to do this
This is not a bug at all. It works properly because stylesInject is just a function from 'styles-inject' package, so we can allow it to be bundled. But it is not bundled because most likely you have this setting in your rollup.config.js:
external: [
/node_modules/
]
So, replace it with a code bellow and it will work:
external: (id) => {
if (/style-inject/.test(id)) return false;
if (/node_modules/.test(id)) return true;
return false;
},
also, you can write a regexp for it instead
You need to do 2 things:
Add style-inject dependency in package.json
"dependencies": {
"style-inject": "^0.3.0"
},
Add following plugin to rollup.config.js
const plugins = [
...
{
/**
* - https://github.com/egoist/rollup-plugin-postcss/issues/381#issuecomment-880771065
* - https://lightrun.com/answers/egoist-rollup-plugin-postcss-esm-library-generated-with-rollup-plugin-postcss-throws-cannot-find-module-node_modulesstyle-in
*/
name: 'Custom Rollup Plugin`',
generateBundle: (options, bundle) => {
Object.entries(bundle).forEach((entry) => {
// early return if the file we're currently looking at doesn't need to be acted upon by this plugin
if (!entry[0].match(/.*(.scss.js)$/)) {
return;
}
// this line only runs for .scss.js files, which were generated by the postcss plugin.
// depending on the use-case, the relative path to style-inject might need to change
bundle[entry[0]].code = entry[1].code.replace(
/\.\.?\/[^\n"?:*<>|]+\/style-inject\/dist\/style-inject.es.js/g,
'style-inject',
);
});
},
}
];
References:
https://github.com/egoist/rollup-plugin-postcss/issues/381#issuecomment-880771065
https://lightrun.com/answers/egoist-rollup-plugin-postcss-esm-library-generated-with-rollup-plugin-postcss-throws-cannot-find-module-node_modulesstyle-in

Uncaught Error: 'target' is a required option - Svelte

I'm building an NPM package for Svelte. With this package I export a couple of simple components:
import SLink from './SLink.svelte';
import SView from './SView.svelte';
export { SLink, SView };
This is before bundling them to a minified version using rollup.
Rollup config:
module.exports = {
input: 'src/router/index.ts',
output: {
file: pkg.main,
format: 'umd',
name: 'Router',
sourcemap: true,
},
plugins: [
svelte({
format: 'umd',
preprocess: sveltePreprocess(),
}),
resolve(),
typescript(),
terser(),
],
};
package.json (minus unnecessary info):
{
"name": "svelte-dk-router",
"version": "0.1.29",
"main": "dist/router.umd.min.js",
"scripts": {
"lib": "rollup -c lib.config.js",
},
"peerDependencies": {
"svelte": "^3.0.0"
},
"files": [
"dist/router.umd.min.js"
],
}
When I publish the package and test it, I get this error:
Uncaught Error: 'target' is a required option
at new SvelteComponentDev (index.mjs:1642)
at new Home (App.svelte:5)
at Z (router.umd.min.js:1)
at N (router.umd.min.js:1)
at new t.SView (router.umd.min.js:1)
at create_fragment (App.svelte:5)
at init (index.mjs:1476)
at new App (App.svelte:5)
at main.js:7
at main.js:9
Which seems to be something to do with mounting the component as target is used to mount:
const app = new App({ target: document.body })
The odd thing is, SLink on it's own works fine, mounts as it should etc., it's just SView that doesn't work.
SLink:
<script lang="ts">
import { writableRoute, changeRoute } from '../logic';
export let name: string = undefined,
path: string = undefined,
query: Record<string, string> = undefined,
params: Record<string, string> = undefined;
let routerActive: boolean;
writableRoute.subscribe(newRoute => {
if (newRoute.path === '*') return;
const matches = (path && path.match(newRoute.regex)) || newRoute.name === name;
routerActive = matches ? true : false;
});
</script>
<div
on:click={() => changeRoute({ name, path, query, params })}
class={routerActive ? 'router-active' : ''}>
<slot />
</div>
SView:
<script lang="ts">
import { writableRoute } from '../logic';
let component: any;
writableRoute.subscribe(newRoute => (component = newRoute ? newRoute.component : null));
</script>
<svelte:component this={component} />
I've tried the components uncompiled, as per these docs, but then the imports don't work.
Anyone have any idea how I can work around this issue?
I recently ran into this problem. I was setting up a new page and forgot the defer keyword on the script. I had:
<script src="/app/public/build/bundle.js"></script>
and it needed to be
<script defer src="/app/public/build/bundle.js"></script>
Notice the missing defer.
All components in a Svelte tree need to be compiled with the same Svelte compiler (i.e. same node_modules/svelte) all at once. This includes components from external libs like what you are trying to achieve. This is what is explained in the docs you've linked.
The main field of the package.json will expose the compiled components, for usage in a non Svelte app.
You also need a svelte field to expose the uncompiled version of the components, for consumption by a Svelte app. You can still export multiple Svelte components from a .js file.
package.json
{
"name": "svelte-dk-router",
"version": "0.1.29",
"main": "dist/router.umd.min.js",
"svelte": "src/index.js",
"scripts": {
"lib": "rollup -c lib.config.js",
},
"peerDependencies": {
"svelte": "^3.0.0"
},
"files": [
"dist/router.umd.min.js"
],
}
src/index.js
import SLink from './SLink.svelte';
import SView from './SView.svelte';
export { SLink, SView };
rollup-plugin-svelte will pick up the svelte field in your lib's package.json, so that import of Svelte components resolve correctly.
I don't know about your issue but I was getting the same error message for this reason:
In the template.html file located in the src folder, I removed the element that has the #sapper ID and replaced the %sapper.html% code inside the body tag. The next thing to do was to change the render target to the document.body, in the client.js file located in the src folder. I forgot to do this step and this was causing that error message.

NPM Module's CSS not applied using #vue/web-component-wrapper VueJS Webcomponents

While developing a Vue web component, using #vue/web-component-wrapper, the styles of npm_modules are not applied. The css actually isn't loaded at all.
Here is my main.js:
import Vue from 'vue';
import wrap from '#vue/web-component-wrapper';
import App from './App.vue';
import '#/modules/filters';
import '#fortawesome/fontawesome-free/css/all.css';
import '#fortawesome/fontawesome-free/js/all';
const wrappedElement = wrap(Vue, App);
window.customElements.define('hello-there', wrappedElement);
Before that, I had the problem, that even my normal css wasn't applied. I resolved this, by help of the answer of this question: Styling not applied to vue web component during development
Even those imported styles in main.js:
import '#fortawesome/fontawesome-free/css/all.css';
won't load at all.
First thought -> there is something wrong with the webpack css-loader/vue-style-loader
Here is my vue.config.js (using the workaround from the above mentioned question):
function enableShadowCss(config) {
const configs = [
config.module.rule('vue').use('vue-loader'),
config.module.rule('css').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('css').oneOf('vue').use('vue-style-loader'),
config.module.rule('css').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('css').oneOf('normal').use('vue-style-loader'),
config.module.rule('postcss').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('postcss').oneOf('vue').use('vue-style-loader'),
config.module.rule('postcss').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('postcss').oneOf('normal').use('vue-style-loader'),
config.module.rule('scss').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('scss').oneOf('vue').use('vue-style-loader'),
config.module.rule('scss').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('scss').oneOf('normal').use('vue-style-loader'),
config.module.rule('sass').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('sass').oneOf('vue').use('vue-style-loader'),
config.module.rule('sass').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('sass').oneOf('normal').use('vue-style-loader'),
config.module.rule('less').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('less').oneOf('vue').use('vue-style-loader'),
config.module.rule('less').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('less').oneOf('normal').use('vue-style-loader'),
config.module.rule('stylus').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('stylus').oneOf('vue').use('vue-style-loader'),
config.module.rule('stylus').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('stylus').oneOf('normal').use('vue-style-loader'),
];
configs.forEach((c) =>
c.tap((options) => {
options.shadowMode = true;
return options;
})
);
}
module.exports = {
chainWebpack: (config) => {
enableShadowCss(config);
},
configureWebpack: {
output: {
libraryExport: 'default',
},
resolve: {
symlinks: false,
},
module: {
rules: [
{
test: /\.ya?ml$/,
use: 'raw-loader',
sideEffects: true,
},
],
},
},
css: {
extract: false,
loaderOptions: {
sass: {
additionalData: `#import "#/styles/_variables.scss";`,
},
},
},
};
So I tried to add css-loader/vue-style-loader manually to webpack with:
chainWebpack: (config) => {
enableShadowCss(config);
config.module
.rule('css')
.test(/\.css$/)
.use('css-loader')
.loader('css-loader')
.end();
},
maybe those styles load now, but it throws an syntax error whilst building anyways:
./node_modules/#fortawesome/fontawesome-free/css/all.css
Syntax Error: CssSyntaxError
(1:4) /Users/.../node_modules/#fortawesome/fontawesome-free/css/all.css Unknown word
> 1 | // style-loader: Adds some css to the DOM by adding a <style> tag
| ^
2 |
3 | // load the styles
I know I know, seems obvious but those lines don't appear in the file at all. Maybe in an imported file though.
Without using the wc-wrapper everything works fine!!
Well anyways... no clue what I should try next. I am a newbie to webpack and Vue!
If anybody has an idea I would greatly appreciate it!
Cheers

Is it possible to just compile SCSS to CSS in vue app?

I'm trying to use some dynamic styling in my Vue.JS application. The idea is to build SCSS themes to static CSS and use it as in my template to switch themes.
Since I'm using bulma as css framework all my styles look the same as:
theme-1.scss
$primary: wheat;
#import 'bulma/bulma';
The current solution is just a bootstrapped vue application with default webpack config. The only time I was able to build scss to css and use it further was the following config:
const path = require('path');
module.exports = {
mode: 'development',
entry: {
functions: [
'./src/index.js',
'./src/scss/theme1.scss',
'./src/scss/theme2.scss'
],
},
output: {
path: path.resolve(__dirname, 'src'),
filename: 'dist/js/[name].js',
},
module: {
rules: [
{
test: /\.scss$/,
use: [
{
loader: 'file-loader',
options: {
name: 'dist/css/[name].css',
}
},
{
loader: 'sass-loader'
}
]
}
]
},
};
Unfortunately I can't use multiple entries in vue app. And it also seems to me is not the right way to work with single page application
Since i'm absolutely new to webpack and vue-loader I think that I do everything wrong and think in the wrong way. Maybe some plugin exists which do definitely the same thing as I want to?
Here is the solution for this issue:
in my vue.config.js I made the following:
module.exports = {
chainWebpack: config => {
config.entry('theme') // you can add here as much themes as you want
.add('./src/theme.scss')
.end();
},
css: {
extract: {
filename: '[name].css', // to have a name related to a theme
chunkFilename: 'css/[name].css'
},
modules: false,
sourceMap: true
},
}
I've typically not used file-loader in my CSS setup; instead I've always used a style-loader, css-loader with MiniCSSExtractPlugin:
https://github.com/webpack-contrib/mini-css-extract-plugin#advanced-configuration-example
Give that setup configuration for MiniCSSExtractPlugin a review.
MiniCSSExtractPlugin is a nice to have, so the most important thing to note is that you are missing the css-loader (which should be used instead of file-loader).

Can't modifyVars dynamically in Less / Nuxt.Js

I'm building a web platform using Nuxt.Js and Ant as a front-end framework.
I saw that it is possible to change the theme of Ant using Less and Less-loader. So I did it before the build with the following code :
antd-ui.js
import Vue from 'vue'
import Antd from 'ant-design-vue/lib'
Vue.use(Antd)
nuxt.config.js
...
css: [
{
src: 'ant-design-vue/dist/antd.less',
lang: 'less'
}
],
...
build: {
transpile: [/^element-ui/],
loaders: {
less: {
javascriptEnabled: true,
modifyVars: {
// You can here change your Ant vars
}
},
},
...
So it works but now I want to implement a Dark Mode so I need to modify the vars dynamically through the code like this :
component.vue
<script>
import less from 'Less'
export default {
...
methods: {
changeTheme() {
less.modifyVars(
...
)
}
...
}
...
But I have the following message in the console :
Less has finished and no sheets were loaded
And nothing has changed... So if you can help me in any way, thanks in advance !