I am trying to use a vue component library, which is loaded by a script tag, within a vue application. That component library is using a webpack setup and uses the setting externals to exclude all vue dependencies. So the host bundle which is using that library has to ship vue and other needed dependencies.
The component library itself is using vue cli with the script vue-cli-service build --target lib to build a lib.umd.js file. Inside the index.html of my host bundle I am just including that file by a script tag. The webpack setup of the host bundle uses config.externals(['#test/my-component-library']) to exclude the component library which is being loaded externally. Inside the host bundle I am using the component library like this:
import { MyComponent } from '#test/my-component-library'
When I run my application, I get the following error: Uncaught SyntaxError: Invalid or unexpected token. This error happens in the following line of my host bundle:
/*!*******************************************************!*\
!*** external "#test/my-component-library" ***!
\*******************************************************/
/*! no static exports found */
/***/ (function(module, exports) {
eval("module.exports = #test/my-component-library;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQHRlc3QvbXktY29tcG9uZW50LWxpYnJhcnkuanMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vZXh0ZXJuYWwgXCJAdGVzdC9teS1jb21wb25lbnQtbGlicmFyeVwiPzFjOTAiXSwic291cmNlc0NvbnRlbnQiOlsibW9kdWxlLmV4cG9ydHMgPSBAdGVzdC9teS1jb21wb25lbnQtbGlicmFyeTsiXSwibWFwcGluZ3MiOiJBQUFBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///#test/my-component-library\n");
/***/ })
Does anyone know why webpack is producing such code and how I can correct it?
I guess I found the solution. So first of all I had a wrong configuration for externals. The correct configuration is:
config.externals({
'#test/my-component-library': {
root: '#test/my-component-library',
commonjs2: '#test/my-component-library',
commonjs: '#test/my-component-library',
amd: '#test/my-component-library',
},
})
In the component library I had to add the webpack configuration config.output.library('MyComponentLibrary') so that the object MyComponentLibrary is globally available in window scope. Then I had to remove the import statement in the host bundle. To register the component I had to add this line to my vue file:
components: {
'MyComponent': MyComponentLibrary.MyComponent,
}
But now I have another problem. The component library itself is using vuetify (which is another component library). But I have also added vuetify to the externals option because my host bundle already contains vuetify. And now I am getting lots of Unknown custom element exceptions. So apparently my component library does not find the vuetify components in my host bundle. Are there any solutions for this problem?
Related
Hi I am using NuxtJS to build a VueJS application. I have installed an image cropping library vue-croppie. I have imported the Vue component as per the documentation like below
import VueCroppie from 'vue-croppie'
However, I am getting the following error on import statement
Could not find a declaration file for module 'vue-croppie'. 'xxx/node_modules/vue-croppie/dist/vue-croppie.cjs.js' implicitly has an 'any' type.
Try npm i --save-dev #types/vue-croppie if it exists or add a new declaration (.d.ts) file containing `declare module 'vue-croppie';
I have tried declaring index.d.ts file at the root of my project with following content but id doesn't solve the problem
declare module 'vue-croppie';
I have tried using require like below as suggested on other posts but of no use
const VueCroppie = require('vue-croppie')
I understand this is a Typescript issue but have no knowledge about Typescript. Can somebody throw more light on this. What is happening and how to fix it.
Thanks
The issue was I had not installed the plugin with VueJS app. Here is how to do it.
VueJS
We can use Vue.use to isntall a plugin before it can be used like below
import Vue from 'vue';
import VueCroppie from 'vue-croppie';
Vue.use(VueCroppie)
NuxtJS
In case of NuxtJS it is little different. The plugin is registered globally, here's how.
Create a file called vue-croppie.js under plugin folder. And add the following to it
import Vue from 'vue'
import VueCroppie from 'vue-croppie'
Vue.use(VueCroppie)
In your nuxt.config.js add this under plugins
{ src: '~/plugins/vue-croppie.js', ssr: false }
Now, the plugin will be available globally. So there is no need to import and can be used directly.
I am currently testing stencil js. For now I want to write stencil components and include them within a VUE/React project. The official website of stencil already shows how to integrate them within a framework (https://stenciljs.com/docs/overview). But they assume that your own stencil component library has already been published to npm.
Is there a way to integrate stencil components locally into a framework to test them without publishing them first?
Yes, you can use npm-link for that.
cd my-component-lib
npm link
cd ../my-app
npm link my-component-lib # or whatever you have named the project in package.json
If you have any problems with that (e. g. with paths not resolving properly), you can also try to pack your package and install the packed version instead, using npm-pack:
cd my-component-lib
npm pack
cd ../my-app
npm install ../my-component-lib/my-component-lib-1.0.0.tgz
Linking is preferable though because changes to your component library will be reflected immediately (after a rebuild), whereas with packing you'd have to re-pack and re-install it after every change to your lib.
Instead of publishing or packing your packages, you could utilize TypeScript's path mapping feature.
This allows you to write your import statements just as you would with a published package, but behind the scenes TypeScript maps the imports to their given source code location.
Here's an example of a tsconfig.json with path mapping added to the compiler options:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"ui-components": ["libs/ui-components"],
"ui-components/loader": ["libs/ui-components/dist/loader/index.cjs.js"],
"ui-components-react": ["generated/ui-components-react/src/components.ts"]
},
...
As you can see, it has 3 mappings: the path to the core Stencil components ui-components, the path to the generated React components which are exposed as ui-components-react, as well as the generated loader ui-components/loader which provides the bridge between the Custom elements and the React wrappers.
I created a full working example for Stencil Web Components with generated bindings and wrappers for React that comes without the need of publishing any package: Nx Stencil React.
Please note that this answer is based on #stencil/core 1.14.0 or below. Future versions may have a different approach on generating the framework integrations.
I've had quite a bit of trouble with this myself so will provide an answer specifically for Vue 3 as Stencil's Framework Integrations guide seems to refer only to Vue 2.
Starting Projects
Stencil Component
Following the Getting Started guide run npm init stencil. Choose the component option.
There was a bug in v2.7.0 so I update to v2.8.0 with npm i #stencil/core#latest --save-exact
Build the project with npm run build
Optional
By default, the stencil project configures multiple build targets, to make it easier to see what build files are being used you can edit the stencil config to only include the custom elements bundle:
\\ stencil.config.ts
outputTargets: [
{
type: 'dist-custom-elements-bundle',
},
{
type: 'dist',
esmLoaderPath: '../loader',
},
],
You also need the 'dist' type for the .d.ts typings file to be generated with your custom-elements (not sure why).
Vue 3 App
Using a globally installed Vue CLI #vue/cli#4.5.13 create a new Vue 3 default project.
Using Stencil in Vue 3
Install your stencil component project
npm install --save ../<path>/stencil-component as a dependency of your vue app.
Fixing NPM Module Resolution
Following the Vue CLI - Troubleshooting guide add a vue.config.js file to the root of your Vue 3 project with the line config.resolve.symlinks(false),
Skipping Component Resolution
In the same file we need to configure Using Custom Elements in View
\\ vue.config.js
module.exports = {
chainWebpack: (config) => {
config.resolve.symlinks(false),
config.module
.rule("vue")
.use("vue-loader")
.tap((options) => ({
...options,
compilerOptions: {
isCustomElement: (tag) => tag.includes("my-"),
},
}));
},
};
Framework Integration
Now we can declare the custom elements, but in the Vue 3 way
\\ main.js
import { createApp } from 'vue'
import App from './App.vue'
import { defineCustomElements } from "stencil-component";
defineCustomElements();
createApp(App).mount('#app');
You can now use your custom component as normal. Here's what my App.vue file looked like after hacking the example starter code:
<template>
<my-component first="Andy" middle="2K" last="11"></my-component>
</template>
<script>
import { MyComponent } from "stencil-component";
export default {
name: 'App',
components: {
MyComponent
}
}
</script>
Errors
No ESLint Config
No ESLint configuration found in /<path>/stencil-component/dist/custom-elements.
Fixed by telling webpack not to resolve symlinks in vue.config.js
Uncaught TypeError: class constructors must be invoked with 'new'
This error occurs in the browser after a successful compilation.
Resolved by telling webpack / vue not to resolve your custom components
Custom Component Not Visible
There are no errors and your component is showing in the DOM inspector but not appearing on the page.
You need to defineCustomElements() in main.js.
Component not found
I've had some variation of this error when trying to import and use my component but haven't been able to reproduce it just now. Doing all of the above and restarting the dev server works fine for me.
For local integration, you can reference the esm.js file inside www/build folder which can be used in the head tag of the Vue/React project.
For eg if you have the below 2 apps
stencil-components - stencil components
stencil-react - sample react app which will consume the components.
Once you run stencil-components by npm run start it will be hosted at 3333 (by default).
Including below line in head ofindex.html of stencil-react will integrate components with live reloading on change.
<script type="module" src="http://localhost:3333/build/stencil-components.esm.js"></script>
I'm converting an existing AngularJS application to a hybrid application, to begin incrementally upgrading it to Angular 4. I want to understand how Angular 4 needs to be referenced in the existing AngularJS. In the existing AngularJS application, it's clear how the AngularJS framework is being loaded - it's simply an included script file:
<script src="/angular/angular.js"></script>
To familiarise myself with an up to date Angular version, I've created a separate 'quickstart' Angular 5 application using the Angular quickstart guide at https://angular.io/guide/quickstart, and I can run it using:
ng serve --open
When I look at the project files though, I'm not seeing where the angular framework is actually being loaded. There is no script being included anywhere in the src/index.html file for that application, it simply declares an directive for a component (app.component.ts) that looks like this:
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Test Angular 5 app';
}
Where does the import actually import from? The Angular documentation says about the index.html file that it is:
The main HTML page that is served when someone visits your site. Most
of the time you'll never need to edit it. The CLI automatically adds
all js and css files when building your app so you never need to add
any or tags here manually.
So what is happening here? How is #angular/core actually being referenced?
When using Angular CLI, we have a lot of moving parts. To really understand what's happening, we should review some of the core technologies used in Angular CLI as they relate to output files and module resolution:
TypeScript -- will transpile your TypeScript code to ES5 or ES6 (most commonly ES5). The transpiled code will use CommonJS modules by default if the target is ES5. If the target is ES6, then ES6 modules will be used.
Webpack -- will take your transpiled TypeScript code and build a dependency graph of your import / exports based on the entry points defined in webpack.config.js. This dependency graph will include all of your modules and it will package these modules into 1+ bundles, depending on the configuration. This is the step where the #angular/core dependency (which lives in node_modules/#angular/core) is processed by Webpack and added to a bundle that can be accessed at runtime.
You can load the generated bundles into the page by including the generated files in your HTML. In the case of JS files, you load them using the script tag like you did with AngularJS.
Because we are using Angular CLI, there are a lot of configurations within Webpack that are set up by default, so we will have multiple generated bundles.
I just created an empty Angular CLI project and inspected the HTML to see the 5 generated bundles:
At the core of the complexity when comparing Angular project files with AngularJS files is that Angular code is transformed through multiple build steps, while AngularJS code is normally used out-of-the-box with ES5. If you were to add Webpack and TypeScript to your AngularJS build, you would see something very similar to the Angular output.
Where is Webpack Configured in Angular CLI?
Under the hood, Angular CLI uses Webpack to build your project and bundle your files.
The Angular team chose not to expose a configurable Webpack configuration file for Angular CLI, but they did add ng eject, which generates a webpack.config.js that matches the Webpack build of your project. The downside of ejecting is that you will no longer use ng serve to serve your project. Instead, you'll use npm run build && npm run start (you'll see these scripts added to your package.json after you eject), which will build and serve your project based on the generated webpack.config.js file.
This feature is essential if you need to make custom modifications to the default Webpack build.
Read more about ng eject here.
Where are the generated bundles?
If you are using ng serve, you won't see any of your generated files because these are being served from memory and not from disk (to speed up development when files are constantly changing), so your generated files are not located in any folder.
If you want to see your generated files, you can run ng build, which will create a dist folder with the index.html and associated assets/bundles.
Note that by default, all commands that build or serve your project will delete your dist folder unless you pass the --no-delete-output-path when building/serving.
You need to understand how imports and exports work in TypeScript
From TypeScript Docs
Exporting a declaration
Any declaration (such as a variable, function, class, type alias, or
interface) can be exported by adding the export keyword.
Validation.ts
export interface StringValidator {
isAcceptable(s: string): boolean;
}
ZipCodeValidator.ts
export const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
Export statements
Export statements are handy when exports need to be renamed for
consumers, so the above example can be written as:
ZipCodeValidator.ts
class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
export { ZipCodeValidator };
export { ZipCodeValidator as mainValidator };
Import
Importing is just about as easy as exporting from a module. Importing
an exported declaration is done through using one of the import forms
below:
Import a single export from a module
import { ZipCodeValidator } from "./ZipCodeValidator";
let myValidator = new ZipCodeValidator();
imports can also be renamed
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();
Import the entire module into a single variable, and use it to access the module exports
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
Since you followed the Angular quickstart guide you must have used npm which is used to install your modules and dependencies.
If you read angular.io/npm
#angular/core: Critical runtime parts of the framework needed by every
application. Includes all metadata decorators, Component, Directive,
dependency injection, and the component lifecycle hooks.
And by default npm stores all your dependencies in node_modules directory.
So you are importing Component from #angular/core which lives inside node_modules directory.
Our project is using the webpack resolve.root option to import modules with absolute paths. (avoiding something like ../../../module)
In its current state the project is using babel-loader which works perfectly fine.
My task is to migrate the app to Angular 2.
Therefor I am currently in the process of transitioning to TypeScript.
Somehow it seems like the ts-loader does not work in combination with the resolve.root option of the webpack config.
Example of the webpack.config.js
resolve: {
root: [
path.resolve('./node_modules'),
path.resolve('./app'),
path.resolve('./app/lib'),
]
},
Example of a module import
import AbstractListState from 'states/abstract_list_state';
The states directory is inside the app/lib directory.
Error when executing webpack
ERROR in ./app/mainViews/panel/panel.controller.ts
Module not found: Error: Cannot resolve module 'states/abstract_list_state' in C:\Users\...\Project\app\mainViews\panel
# ./app/mainViews/panel/panel.controller.ts 4:28-65
Pre version 2.0 TypeScript will try to load modules with an absolute path from the node_modules directory. This is because TypeScript's module resultion is per default set to "node". Which means it works like node's require method. So, even if you're using webpack to build your app, TypeScript (and its compiler) will still want to load the files.
In order to let webpack import your modules with absolute path you have to go back and use the require method. This way TypeScript will let webpack import stuff. But of course you will not get any type-inference, autocomplete, ...
Or, you update to the TypeScript 2.0 beta and give this a try: https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#module-resolution-enhancements-baseurl-path-mapping-rootdirs-and-tracing
Context
I'm working on a project using Angular2, TS and SystemJS. I've built a simple module loader using SystemJS which allows me to import 'custom' modules like this :
import { NavComponent } from "component#nav";
Maybe it'll be important.
Goal
I would like to import .scss files inside my angular components to be able to do things like that :
#Component({
selector: 'app-view',
styles: [ require('path/to/mainScss/file.scss') ],
template: //...
})
Issue
I've found some examples using webpack but I can't get this works. During searching, I've found this module. It can be interesting because it uses SystemJS like me and I don't have to add webpack to my project.
So I've decided to use it (and I'm not sure if it's possible to use webpack and SystemJS at the same time. require was undefined). I didn't know jspm before using this module, I've always used npm. So I've installed jspm locally (relative to my project) and globally and I've installed the module.
Then, inside a component : import './styles/importer.scss!';. And I get an error here.
Error: SyntaxError: Unexpected token <. I know that this error happens when SystemJS failed to load a module but I can't figure out why.
Why ?!
Why am I trying to do that ?
My module loader allows me to write some modules in separate folders (named like this namepace#name). I can add those folders inside another one named modules/ and then enable/disable modules by editing a configuration file.
So I want my .scss files to be loaded only if a module is enabled. That's why I want to include my sass inside an Angular component. That way, it'll be loaded only if the component is loaded.