how to use a library of Vue3 components as standalone Web Components? - javascript

I have a set of Vue3 components in a Vite architecture and want to publish them as a lib of pure web components. how can i use my built files to do so ?
I followed the Vite docs to build a lib. It says to add this in the vite.config.js file:
const path = require('path');
const {defineConfig} = require('vite');
const vue = require('#vitejs/plugin-vue');
// https://vitejs.dev/config/
export default defineConfig({
build: {
lib: {
entry: path.resolve(__dirname, 'src/components/index.js'),
name: 'TykaynLib',
fileName: (format) => `tykayn-lib.${format}.js`,
},
rollupOptions: {
external: ['vue'],
output: {
// Provide global variables to use in the UMD build
// Add external deps here
globals: {
vue: 'Vue',
TykaynLib : 'TykaynLib'
},
},
},
},
plugins: [vue()],
});
So after the build i get ES and UMD files containing my components, but they are not standalone, they require VueJS to be able to work.
I wanted pure web components that i could use by just loading a script and having to just add my custom HTML tags inside an HTML file. I do NOT want to make a plugin for vue or require the end users of my components to have to use Vue.
Is there a way to build this ?

Related

Why is the file `vite.svg` in the `dist` folder when building with Vite library mode?

I am trying to publish a React component as an npm module using the library mode of Vite. But even though my entry file does not import or use the image vite.svg it is copied to the dist folder.
vite.config.js:
export default defineConfig({
plugins: [react()],
build: {
lib: {
// Could also be a dictionary or array of multiple entry points
entry: resolve(__dirname, 'src/index.jsx'),
name: 'MyLib',
// the proper extensions will be added
fileName: 'my-lib',
},
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ['react', 'react-dom'],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
},
})
src/index.jsx:
import React from 'react';
export function Button(props) {
return <button {...props} />;
}
dist
📂dist
┣ 📜my-lib.js
┣ 📜my-lib.umd.cjs
┗ 📜vite.svg
By default, Vite will automatically copy all files inside publicDir into the outDir when building. The default value of publicDir is public and this folder (with the file vite.svg) is created when starting a new project with npm create vite.
The easiest way to get rid of this file is to just delete the public folder.
There is also an option copyPublicDir that can be set to false in order to disable this behavior and not copy any files from the set publicDir to outDir.

environment variables in babel.config.js

We need to modify certain configuration/variables in our React Native app (built using Expo) depending on environment (local/dev/staging/production). I've looked at a number of libraries meant for this purpose but all seem to have a flaw that breaks for our use case:
dotenv (breaks because it tries to access 'fs' at runtime, when it's no longer available since it's not pure JS package and can't be bundled by Expo)
react-native-config (can't use it with Expo because it needs native code as part of the plugin)
react-native-dotenv (kinda works but caches config internally and ignores any .env changes until the file importing the variable is modified)
As a cleaner alternative that does not require third party plugins, I'm considering using babel's env option and just listing all of the environments as separate json objects within babel.config.js. I'm not seeing much documentation or examples on this, however. Do I just add env field at the same level as presets and plugins that contains production, development, etc. fields as in example below:
module.exports = (api) => {
api.cache(true);
return {
presets: [...],
env: {
development: {
CONFIG_VAR: 'foo'
},
production: {
CONFIG_VAR: 'bar'
}
}
}
}
Would that work? And how would I access this CONFIG_VAR later in the code?
I just ran into the same issues when trying to setup environment variables in my Expo project. I have used babel-plugin-inline-dotenv for this.
Install the plugin
npm install babel-plugin-inline-dotenv
Include the plugin and the path to the .env file in your babel.config.js file
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
env: {
production: {
plugins: [["inline-dotenv",{
path: '.env.production'
}]]
},
development: {
plugins: [["inline-dotenv",{
path: '.env.development'
}]]
}
}
};
};
In your .env.production or .env.development files add environment variables like this:
API_KEY='<YOUR_API_KEY>'
Later in your code you can access the above variable like this:
process.env.API_KEY
To access your env variables within the babel.config.js file, use the dotenv package like this:
require('dotenv').config({ path: './.env.development' })
console.log('API_KEY: ' + process.env.API_KEY)
module.exports = function() {
// ...
}

Webpack and toastr

I am trying to load and bundle toastr as a dependency using webpack.
here is the entire webpack config file
var path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var webpack = require('webpack');
const DEVELOPMENT = process.env.NODE_ENV === 'development';
const PRODUCTION = process.env.NODE_ENV === 'production';
module.exports = {
entry: {
main: './wwwroot/js/mainEntry.js',
vendor: ['jquery', 'tether',
'bootstrap', 'jquery-colorbox',
'jquery-confirm', 'jquery-validation',
'jquery-validation-unobtrusive',
'toastr', 'jquery.nicescroll',]
},
output: {
filename: '/js/[name].js',
path: path.resolve(__dirname, 'wwwroot'),
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test: /\.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader?name=[name].[ext]&publicPath=/fonts/&outputPath=/fonts/'
},
{
test: /\.(png|jpe?g|gif|ico)$/,
loader: 'file-loader?name=[name].[ext]&publicPath=/images/&outputPath=/images/'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
// (the commons chunk name)
filename: "/js/vendor.js",
// (the filename of the commons chunk)
minChunks: 2,
}),
new ExtractTextPlugin({
filename: 'css/[name].min.css'
}),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
],
};
and my entry js file as
//JS
import 'jquery-colorbox';
import 'slick-carousel';
import toastr from 'toastr';
//CSS
import './../../node_modules/bootstrap/dist/css/bootstrap.css';
import './../../node_modules/slick-carousel/slick/slick.css';
import './../../node_modules/jquery-colorbox/colorbox.css';
import './../../node_modules/toastr/build/toastr.css';
import './../../node_modules/jquery-confirm/css/jquery-confirm.css';
import './../../node_modules/font-awesome/css/font-awesome.css';
import './../../Modules/Components/Menu/menu.css';
import './../../wwwroot/css/lahuritv.css';
The bundle is created without any errors. And I can see that the toastr script is included in the bundle when I look at the output bundle. But the problem is that the variable toastr is not available in the browser window.
I tried looking for similar issue but couldn't find any. This is my first time trying to learn webpack. Any help is appreciated.
Simple solution :
//app.js
import toastr from 'toastr'
window.toastr = toastr
Webpack does not expose the modules you import, it bundles your code together with the needed dependencies, but they are still modular and have their own scope. Webpack does not just blindly combine the sources of all the files and certainly does not make everything global. If the entry file you posted is the whole file, then you're not doing anything at all.
Instead with webpack you would do the work in the JavaScript files you bundle. So to speak, all you include in your HTML are the bundles created by webpack, you don't include other scripts that try to access something in the bundle, but you rather do it directly in your bundle.
Of course you could expose it to window by explicitly defining it: window.toastr = toastr after importing toastr, but polluting the global scope is generally not a good idea and it's no different from just including toastr in a <script> tag. If the bundle is just supposed to be used as a library you could have a look at Authoring Libraries. But I think you're just doing a regular web app and you should get all the code together to be bundled by webpack and not rely on it in another <script> tag.
You should go through the Getting Started guide of the official docs. The example is very tiny (creates one DOM element) but shows you the concept of webpack apps and also uses an external dependency.

How to exclude vendor module peerDependencies from export in Webpack?

Question
When exporting a bundle in Webpack, how can I exclude 3rd-party module's peerDependency? (not 3rd-party module itself)
Background
I would like to create an UIkit with customized components on top of angular-material framework. With Webpack, I can bundle my customzied components and angular-material together into something like uikit.js, and then port to other application later on. However, I don't want to include angular module itself into this uikit.js.
Issue
It seems that Webpack is "clever" enough to notice that angular module is a dependency of angular-material module, and thus would export both angular module and angular-material module to the bundle. One can either use config.externals: {'angular': 'angular'} or new webpack.IgnorePlugin(/angular$/) to exclude angular module which are explicitly require in the app, but for peerDependency (i.e. the one require inside angular-material), it would still include it.
So, how could I exclude this 3rd-party depended modules out from export?
Example:
// app.js
var angular = require('angular');
var material = require('angular-material');
// ... other application logic
// webpack.config.js
var webpack = require('webpack');
module.exports = {
entry: {
app: './app.js'
},
module: {
loaders: [
// some module loaders
]
},
// This only excludes the angular module require by the app, not the one require by the angular-material
externals: {'angular': 'angular'},
plugins: [
// This is the same as externals, only the one required by app.js would be excluded
new webpack.IgnorePlugin(/angular$/)
]
};
In the webpack (version 4) config, I export vendor apps into a vendor bundle and chunk all like this:
entry: {
app: './app.js',
vendor: [ 'angular', 'angular-material', '...' ],
},
optimization: {
splitChunks: {
chunks: 'all',
}
},
Basically, this indicates which chunks will be selected for optimization and all means that chunks can be shared even between async and non-async chunks. Furthermore, How your modules are built and how you handle dependancies can further streamline your chunk size.
In addition you can provide a function whose return value will indicate whether to include each chunk:
module.exports = {
//...
optimization: {
splitChunks: {
chunks (chunk) {
// exclude `my-excluded-chunk`
return chunk.name !== 'my-excluded-chunk';
}
}
}
};
Here is a link to webpack explaining the splitChunks plugin.

How to shim tinymce in webpack?

I'm trying to get tinymce recognized by webpack. It sets a property named tinymce on window, so evidently one option is to require() it using syntax like this (described at the bottom of the EXPORTING section of the webpack docs):
require("imports?window=>{}!exports?window.XModule!./file.js
But in this example, how is ./file.js resolved? I installed tinymce via npm, and I can't figure out how to specify the right path to the tinymce.js file.
Regardless, I'd rather handle this in my configuration and be able to just require('tinymce') if possible, so I've installed exports-loader and added the following to my configuration (based on this discussion):
module: {
loaders: [
{
test: /[\/]tinymce\.js$/,
loader: 'exports?tinymce'
}
]
}
Unfortunately this isn't working. What's wrong with my configuration?
The tinymce module on npm can't be required directly, but contains 4 different distributions of the library. Namely:
tinymce/tinymce.js
tinymce/tinymce.min.js
tinymce/tinymce.jquery.js
tinymce/tinymce.jquery.min.js
To be able to do require('tinymce') in your code, you can add an alias in your webpack config, as well as a custom loader for your distribution of choice.
resolve: {
alias: {
// require('tinymce') will do require('tinymce/tinymce')
tinymce: 'tinymce/tinymce',
},
},
module: {
loaders: [
{
// Only apply on tinymce/tinymce
include: require.resolve('tinymce/tinymce'),
// Export window.tinymce
loader: 'exports?window.tinymce',
},
],
},
Where you can replace tinymce/tinymce with your distribution of choice.
Just like #cchamberlain I ended up using script loader for tinymce, but to load the plugins and other resources that were not required by default I used CopyWebpackPlugin instead of ES6 for more configurable solution.
var copyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
//...
plugins: [
new copyWebpackPlugin([
{ from: './node_modules/tinymce/plugins', to: './plugins' },
{ from: './node_modules/tinymce/themes', to: './themes' },
{ from: './node_modules/tinymce/skins', to: './skins' }
])
]
};
I was able to integrate tinyMCE in my Angular 2/TypeScript based project by using the imports-loader and exports-loader and the copy-webpack-plugin.
First ensure that the necessary dependencies are available and part of the packages.json file of your project:
npm i tinymce --save
npm i exports-loader --save-dev
npm i imports-loader --save-dev
npm i copy-webpack-plugin --save-dev
Then add the required loader to the loaders-section of your webpack configuration:
loaders: [
{
test: require.resolve('tinymce/tinymce'),
loaders: [
'imports?this=>window',
'exports?window.tinymce'
]
},
{
test: /tinymce\/(themes|plugins)\//,
loaders: [
'imports?this=>window'
]
}]
To make the copyWebpackPlugin available in your webpack configuration, import it in the header part of the webpack configuration file:
var copyWebpackPlugin = require('copy-webpack-plugin');
And, as Petri Ryhänen commented, add the following entry to the plugins-section of your webpack configuration:
plugins: [
new copyWebpackPlugin([
{ from: './node_modules/tinymce/plugins', to: './plugins' },
{ from: './node_modules/tinymce/themes', to: './themes' },
{ from: './node_modules/tinymce/skins', to: './skins' }
])
]
This step ensures that (required) addons of tinyMCE are also available in your webpack.
Finally to import tinyMCE in your Angular 2 component file, add
require('tinymce')
declare var tinymce: any;
to the import section and tinyMCE is ready to use.
I got this to work similar to how I bundle React to ensure I don't get two separate instances in DOM. I had some issues with imports / exports / expose loaders so instead I used script-loader.
In my setup I have a commons chunk that I use strictly for vendors (React / tinymce).
entry: { 'loading': '../src/app/entry/loading'
, 'app': '../src/app/entry/app'
, 'timeout': '../src/app/entry/timeout'
, 'commons': [ 'expose?React!react'
, 'expose?ReactDOM!react-dom'
, 'script!tinymce/tinymce.min.js'
]
}
This is working for me the same way that including the script from CDN would work however I now had errors because it could not find my themes / plugins / skins paths from my node_modules location. It was looking for them at paths /assets/plugins, /assets/themes, /assets/skins (I use webpack public path /assets/).
I resolved the second issue by mapping express to serve these two routes statically like so (es6):
const NODE_MODULES_ROOT = path.resolve(__dirname, 'node_modules')
const TINYMCE_PLUGINS_ROOT = path.join(NODE_MODULES_ROOT, 'tinymce/plugins')
const TINYMCE_THEMES_ROOT = path.join(NODE_MODULES_ROOT, 'tinymce/themes')
const TINYMCE_SKINS_ROOT = path.join(NODE_MODULES_ROOT, 'tinymce/skins')
router.use('/assets/plugins', express.static(TINYMCE_PLUGINS_ROOT))
router.use('/assets/themes', express.static(TINYMCE_THEMES_ROOT))
router.use('/assets/skins', express.static(TINYMCE_SKINS_ROOT))
After doing this window.tinymce / window.tinyMCE are both defined and functions same as CDN.
As an addition to this answer (thanks to Petri Ryhänen), I want to add my copyWebpackPlugin and tinymce.init() configuration adjustments.
new copyWebpackPlugin([{
context: './node_modules/tinymce/skins/lightgray',
from: './**/*',
to: './tinymce/skin',
}]),
With this configuration you will get all skin files in {output}/tinymce/skin folder.
Then you can initialize tinymce like this:
import tinymce from 'tinymce/tinymce';
// A theme is also required
import 'tinymce/themes/modern/theme'; // you may change to 'inlite' theme
// Any plugins you want to use has to be imported
import 'tinymce/plugins/advlist/plugin';
// ... possibly other plugins
// Then anywhere in this file you can:
tinymce.init({
// ... possibly other options
skin_url: '/tinymce/skin', // <-- !!! here we tell tinymce where
// to load skin files from
// ... possibly other options
});
With this I have both development and production builds working normally.
We use TinyMCE jQuery 4.1.6 and the accepted answer did not work for us because window seems to be used in other locations by TinyMCE (e.g. window.setTimeout). Also, document not being shimmed seemed to cause problems.
This works for us:
alias: {
'tinymce': 'tinymce/tinymce.jquery.js'
}
module: {
loaders: [
{
test: /tinymce\/tinymce\.jquery\.js/,
loader: 'imports?document=>window.document,this=>window!exports?window.tinymce'
}
]
}
Load your plugins like this:
{
test: /tinymce\/plugins/,
loader: 'imports?tinymce,this=>{tinymce:tinymce}'
}

Categories