Im working on a JavaScript library and I want future users to be able to pick and choose the plugins they want to add to their project among with the main library. I'm having few issues with modules and webpack. I'm writing pseudo code to give an idea of how the code is organized.
My index.js for the main library looks like this:
import ClassA from "./classA";
import ClassB from "./classB";
export default class MyLib {
.....
}
export { ClassA, ClassB }
I can easily output the library with webpack:
output: {
path: ...
filename: 'mylib.min.js',
library: "MyLib",
libraryTarget: "umd"
}
To be able to choose which plugins to add, I'm creating different npm packages (one for each plugin), adding MyLib as an external dependency and then doing:
import {ClassA, ClassB} from "MyLib";
class PluginA extends ClassB {
constructor() {
this.test = new ClassA();
}
}
This works perfectly but, when "compiling" PluginA, webpack would include MyLib in the final js file for PluginA. If I want to include multiple plugins the code would end up with multiple copies of the main lib.
My final goal is to organize the code in such a way that can be easily installed with the following npm commands without having duplicated code everywhere:
npm install MyLib
npm install MyLib-PluginA
npm install MyLib-PluginB
Of course, one obvious solution would be to not use webpack for the plugins but I'd like to keep this option as the last resource in case nothing else works.
Thanks!
I wouldn't recommend using webpack to build your plugins/library. Rather, I'd let the consumer of the library decide on their own bundler. Your best step for the library should just be transpilation (if needed) of any intermediate code like babel-featured JS or TypeScript into something that can be safely require'd by node.
In addition, each plugin ought to have MyLib as a peerDependency instead of a regular dependency. That will make sure that MyLib doesn't get nested inside of the plugin's node_modules and will thus avoid duplicates being bundled. The plugins could in addition has MyLib as a devDependency for the sake of unit tests, but the important bit is that it's never a regular dependency.
Digging into webpack documentation, I've found a solution that uses webpack's externals.
From webpack documentation:
The externals configuration option provides a way of excluding dependencies from the output bundles.
I've just added the following lines to the webpack's configuration for the plugin:
module.exports = {
...,
externals: {
mylib: {
commonjs: 'MyLib',
commonjs2: 'MyLib',
amd: 'MyLib',
root: 'MyLib'
}
}
};
Webpack documentation: https://webpack.js.org/configuration/externals/
Hope this will help others.
Related
I want to unit test some ES6 classes that are stored as modules. However, when I try to run my tests, import triggers an error message: Cannot use import statement outside a module which means I can't test my modules.
All the Jasmine examples use the old ES5 syntax with classes defined as functions using the modules.export and then imported using the require function. I've found websites such as this where they seem to use the ES6 and import { module } from 'path' syntax, but I have no idea why this works and why mine doesn't? Is it because I'm not testing using a headless browser? It is because Jasmine doesn't support this functionality natively and I need some other package? Is it because I need to use jasmine-node in some specific way to get it to work? Should I not be using ES6 classes at all?
As its probably obvious I'm a beginner to node and javascript but I wouldn't have thought that just testing the functionality of a class would be this difficult so if anyone could give me some guidance on how to accomplish testing a class module in Jasmine I would be really grateful.
For example I have a module.
// src/myClass.mjs
export class MyClass {
constructor() { }
}
I have a simple Jasmine unit test.
// spec/myClassSpec.js
import { MyClass } from '../src/myClass.mjs'
describe('my class', function() {
var myClassInstance
beforeEach(function() {
myClassInstance = new MyClass()
})
it('is an instance of MyClass', function() {
expect(myClassInstance).toBeInstanceOf(MyClass)
})
})
I may be late to answer however using "import" statement in Nodejs is not as simple as it looks.
There are few main differences between require and import (like being async) thus one can not be simply converted into another.
**
A simple solution is to use rollup
**.
Once you have completed your module your can use rollup to bundle this module as a commonjs file. That file can then be used for testing.
To install rollup
npm install rollup --save-dev
After rollup in installed you need to add following commands to the script of "package.json" file
"dist": "rollup -c"
You need to have a rollup.config.js file in the root folder of your application.
The content of rollup.config.js are:
import { rollup } from "rollup";
export default {
input: './index.js',
output: {
file: 'dist/index.js',
format: 'cjs' //cjs stands for common js file
}
};
I have used node to manage dependencies on React apps and the like, in those you use package.json to keep track of libs and use them in your scripts using ES6 import module syntax.
But now I'm working on a legacy code base that uses a bunch of jQuery plugins (downloaded manually and placed in a "libs" folder) and links them directly in the markup using script tags.
I want to use npm to manage these dependencies. Is my only option:
run npm init
install all plugins through npm and have them in package.json
link to the scripts in the node_modules folder directly from the markup:
<script src="./node_modules/lodash/lodash.js"></script>
or is there a better way?
Check out this tutorial for going from using script tags to bundling with Webpack. You will want to do the following: (Do steps 1 and 2 as you mentioned in your question then your step 3 will change to the following 3 steps)
Download webpack with npm: npm install webpack --save-dev
Create a webpack.config.js file specifying your entry file and output file. Your entry file will contain any custom JS components your app is using. You will also need to specify to include your node_modules within your generated Javascript bundle. Your output file will be the resulting Javascript bundle that Webpack will create for you and it will contain all the necessary Javascript your app needs to run. A simple example webpack.config.js would be the following:
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
resolve: {
alias: {
'node_modules': path.join(__dirname, 'node_modules'),
}
}
};
Lastly, add a <script> tag within your main HTML page pointing to your newly generated Javascript bundle:
<script src="dist/my-first-webpack.bundle.js"></script>
Now your web application should work the same as before your refactoring journey.
Cheers
I recommend Parcel js.
Then you only need:
Run npm init
Install dependency, for example npm install jquery
Import with ES6 syntax: import $ from "jquery";
And run with parcel
something which is bothering me when writing tests in my folder structure is the following thing:
//App
meteor/imports/api/tasks.js
//test
meteor/test/imports/api/tasks.test.js
So now when i import something from tasks.js i go like import { task } from '../../../imports/api/tasks.js' and my folder structure gets much bigger than this.
is there a better solution?
I was thinking of an import hook, maybe in the root tests directory, so i can import all the things from there, and when i am on the test, i can import from the import hook and don't have to do all the ../../../../ navigation.
If you are using babel, you can add babel-plugin-module-resolver to your babel configuration.
A Babel plugin to add a new resolver for your modules when compiling
your code using Babel. This plugin allows you to add new "root"
directories that contain your modules. It also allows you to setup a
custom alias for directories, specific files, or even other npm
modules.
The module resolver may collide with webpack2 module handling, so you'll want to limit it just to tests:
.babelrc example:
"env": {
"test": {
"plugins": [
["module-resolver", {
"root": ["./meteor/imports"]
}]
]
}
}
Use
/imports/api/tasks.js
instead of
../../../imports/api/tasks.js
The / to start with marks root.
I am working on a project containing a Vuex module and an abstract components that users can extend from.
I would love to publish this on NPM to clean up my codebase and pull this away from my project as a solid well tested module. I have specified the main file in package.json to load an index which imports everything I want to expose:
https://github.com/stephan-v/vue-search-filters/
The index contains this at the moment:
import AbstractFilter from './src/components/filters/abstract/AbstractFilter.vue';
import Search from './src/store/modules/search';
module.exports = {
AbstractFilter,
Search
};
For this to work I need to transpile this since a babel compiler normally won't transpile files imported from node_modules(Correct me if I am wrong here). Besides that I would probably be a good idea to do this so it can be used by different systems.
How do I transpile only the files that I need though with Webpack? Do I have to create a separate config for this?
What does a config like that look like? I know the vue-cli has a build command for one single file component but this is a bit different.
Any tips or suggestions on how to transpile something like this are welcome.
Edit
This seems like a good start as well:
https://github.com/Akryum/vue-share-components
The most import thing for Webpack users to notice is that you need to transpile your files in UMD which can be set by:
libraryTarget: 'umd'
This will make sure your are transpiling for Universal Module Definition, meaning your code will work in different environments like AMD,CommonJS, as a simple script tag, etc.
Besides that it is import to provide the externals property in webpack:
externals: {}
Here you can define which libraries your project users but should not be built into your dist file. For example you don't want the Vue library to be compiled / transpiled into the source of your NPM package.
I will research a bit more, so far the best options looks like to create a custom project myself If I want flexibility and unit testing.
Webpack docs
This is also a useful page which goes in depth about how to publish something with Webpack:
https://webpack.js.org/guides/author-libraries/#add-librarytarget
The best way probably will be to build the module and set main in your package.json to my_dist/my_index.js. Otherwise every project that will use your module will have to add it to include which is tedious.
You will also want your webpack build to follow UMD (Universal Module Definition). For that you must set libraryTarget to umd:
...
output: {
filename: 'index.js',
library:'my_lib_name',
libraryTarget: 'umd'
},
...
Also a good thing will be to add Vue to externals so that you didn't pack extra 200kb of vue library.
externals: {
vue: 'vue'
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
And add it to peerDependencies in package.json:
...
"peerDependencies": {
"vue": "^2.0.0"
},
"devDependencies": {
"vue": "^2.0.0"
}
...
If you need an existing example of how to pack a vue.js component, you can take a look in one of the modules I maintain:
https://github.com/euvl/vue-js-popover
Particularly webpack.config.js and package.json will be interesting for you.
I was searching for a similar solution and found rollup https://github.com/thgh/rollup-plugin-vue2 (but was not able to make it work) and this component https://github.com/leftstick/vue-expand-ball where all the component code gets compiled to one reusable js-file.
I know that's not a proper solution but maybe it's sufficient for your needs.
I really hope this isn't an opinion question, and I don't think it is...
Is there a correct way to include a JS module in a TS project? If I don't do one of the 2 steps below, my IDE (linter, wahtever) gives me TS2307: Cannot find module when I use the line import * as myPackage from 'myPackage'.
The steps I use are
npm i thePackage --save
typings i -g thePackage --save // assuming it has a typings
If the module doesn't have a typings, all you do is
declare let myPackage:any
Is that all you have to do in order to use an external package in a TS project? Or am I missing a vital step?
I know I could create a .d.ts file, but for now, I'd like to get the hang of TS without spending all my time writing those files.
I've also been reading about DefinitelyTyped and #types/myPackage, but I don't know much about it...
If the package has declaration files
In TypeScript 2.0+, for a package the-package
npm install --save #types/the-package
If the package doesn't have declaration files
If the package doesn't have declaration files for it, create a file named externals.d.ts with the following:
// This file should contain no top-level imports or exports.
declare module "the-package";
Note: If there's more than one thing you can import from the-package, you can use a wildcard as well:
declare module "the-package/*";
At this point, you can do basically whatever you want with the package when you import it - it comes in as any.
Strongly typing the package
If you want to add some type safety, we'll start defining the module in its own file in a folder called externals.
So we'll write out ./externals/the-package/index.d.ts.
export function foo(): void;
export function bar(x: string): number;
And now add the following to your tsconfig.json's compiler options.
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"*": ["externals/*"]
}
}
}
Now any modules should be looked up in externals first before being looked up in #types & node_modules.
Note: This assumes externals is in the same directory as tsconfig.json. Adjust appropriately.
Once your module is fully written out, you can send a PR for this package to DefinitelyTyped if you'd like to help others out.