Import components outside the root folder with babel - javascript

I have multiple projects in react in a monorepo: app1 app2 app3, and each app is built with webpack and babel.
I want to create a shared directory with shared components between the apps, so I created in the monorepo another directory shared.
( I executed in each project npm i ../shared )
But when I import componets from shared inside one the of the apps, babel throws an exception: Add #babel/preset-react (https://git.io/JfeDR) to the 'presets' section of your Babel config to enable transformation. If you want to leave it as-is, add #babel/plugin-syntax-jsx (https://git.io/vb4yA) to the 'plugins' section to enable parsing.
NOTE: if I import simple functions from shared it works, only when importing components it does not work.
What configuration can I add so I can create a shared directory with shared components?
The .babelrc file I have in each app looks like this
{
"presets": [
[
"#babel/preset-env",
{
"targets": {
"browsers": ["chrome >= 50"]
},
"useBuiltIns": "usage",
"corejs": 3
}
],
"#babel/preset-react",
"#babel/preset-typescript"
],
"plugins": ["#babel/plugin-syntax-jsx", "#babel/plugin-transform-react-jsx", "#babel/plugin-transform-runtime"]
}

It appears, this is more of a webpack and less of a babel problem. The error pops up, because your babel-loader is not configured correctly.
When adding "external folders/modules" to your build, you usually want to do (or at least check) the following:
Add the shared folder's src folder to your babel-loader's include (if it is not already included)
-> This makes sure, things get babel'ed nicely, and your preset-react will actually work).
-> Make sure not to include too much (e.g. never include any node_modules folder in its entirety, or things will slow down abysmally).
You might also have to add the shared folder to your babel-loader's babelrcRoots, because else, it will ignore its .babelrc.js file (if it is different from your babel-loader's default config), and thus only use default settings, ignoring your preset-react.
Often times, you also need to add the shared folder's src folder to your webpack.config's resolve.modules:
-> This allows webpack to do it's resolve magic inside that folder as well.
If it has its own node_modules, also add that to resolve.modules.
For more info on this particular issue, see here.

You can create a link to an external package:
Installing a local module using npm
Another possibility if you are working in a team is the more sophisticated approach:
Get npm module via Git
As for the declarations of the plugins, see documentation (you need a "plugins" array, see link):
babel js plugins

Related

How to compile/build a TypeScript library that uses no NodeJS API/Module dependency to support browser

I wrote a few code that use plain browser Javascript APIs only and can be run well within browser HTML (served by IIS Server or Chrome Extensions). Now I want to contribute to the community by writing a library I have not seen on the market yet. However looking at current solutions, I am at loss at how a project is even built (WebPack/Browserify etc). A side note: I never actually work with NodeJS/NPM before.
For example I have this TypeScript project with the main file AwesomeClass.ts like this:
import { Helper1 } from "./Helper1.js";
import { Helper2 } from "./Helper2.js";
export class AwesomeClass {
doSomething() {
new Helper1().doSomething();
new Helper2().doSomething();
}
}
When built with tsc (I use VS Code as IDE), I can perfectly put this inside an Javascript module and browser can run it.
import { AwesomeClass } from "./AwesomeClass.js";
// Do something with AwesomeClass
So my question is, how do I build and distribute AwesomeClass? Maybe no NPM needed, but from a CDN? Ideally, I think somehow I should have the following output in a dist folder and developer can refer them either by hosting the files by themselves or use a CDN:
awesomeclass.js: For those who want to just use AwesomeClass without module feature (I think it's called UMD?). I.e. expose the AwesomeClass to global scope.
awesomeclass.es6.js: For those who want to use AwesomeClass by using import statement, like import { AwesomeClass } from "https://cdn.example.com/awesomeclass.es6.js";. I like this approach best and want to use this.
I should have something like awesomeclass.d.ts so those using TypeScript can use it. This one is especially tricky because so far I still don't understand how to make it work for 2nd scenario. TypeScript cannot get the type from an import statement from Javascript, and even ignoring that, I cannot get any typing for import statements.
In all cases, I would rather have only one js/ts file packed together if possible but not a deal breaker if I cannot (i.e. user will have to download Helper1.js and Helper2.js as well if I cannot).
Here's my current tsconfig.json:
{
"compileOnSave": true,
"compilerOptions": {
"noImplicitAny": true,
"target": "ES2020",
"module": "ES2020",
"declaration": true
},
"exclude": [
"node_modules"
]
}
There's quite a lot of things you're asking and each of them have several answers so I'll try to provide you with a bit of an overview of your options.
Compiling everything to a single file
One of the things you asked is how you can compile everything to a single file. You can do that in 2 different ways, either using webpack to bundle it for client or using typescript directly.
If you use typescript you have to set outFile to a specific file, in which case it will compile everything to that file, however you can only do that if your module is also set to amd or system, both of which are not ideal. While this works it's something I'd suggest you don't use.
Instead you should use webpack to bundle all your stuff with the output option, in which case webpack will use ts-loader to invoke typescript for you, compile your stuff and bundle it into a single file.
You should also note here that this is only applicable if you actually want to serve it through web and not if you're building a library. If you're building an npm package that you're planning on letting people install with something like npx packageName so that you can use it like import somePackage from some-package, then you should be compiling your stuff to a /lib directory into normal javascript and just let them import it as javascript. There's no reason for why you should provide them with the original typescript in that case.
How to build and distribute it
It really depends on what exactly you're building and how it'll be used, however overall you have 2 main options.
You can either host it somewhere on some server with a domain of your choice so that people can download it. Or you can put it anywhere like a normal git repo where people can download it. In this case you'll have to compile it with webpack yourself, upload it yourself and then just share the link with people i.e. https://example.com/downloads/awesome. Alternatively you can use webpack to render it server side and expose an API to people that they can call in order to get your code, then it will deliver the bundled javascript to them once they call the API i.e. https://api.example.com/awesome which will hit your API with a GET request, which will route to awesome and then you invoke webpack's compiler to bundle your code server side.
Your other option is to build your package like normal, compile it and then use the official npm registry to host your npm package. Using this option will allow people to npx package or npm i package on your code and also allow them to use it like import awesome from 'awesome'. If you go this route then using webpack isn't necessary, or it depends, because people using it will import it into their own project and build it into their own webpack setup and bundle if required. In this case all you have to do is compile your typescript to something like a /lib and allow them to install and import it.
From the things that you're asking/saying it seems to me that you're trying to create an npm package, for that all you need is to create the package, compile your typescript, set up an account on npm and push your package to their registry, from where you can let anyone install it. For this you also shouldn't care at all about compiling all your code to a single file because it doesn't matter, if they use import awesome from 'awesome' then that file can again import anything else inside your own package and they wouldn't know it. You can just tsc your code to an output directory and let them know which is the default export for that package.
If your code has to run in browser then I don't believe just using typescript will be enough, in that case you'll have to use webpack, you might also need babel if you need to support older browsers and polyfills, which is something you can add to webpack, then you'll use webpack to compile your bundle. Webpack will then invoke typescript, through ts-loader, which will compile and bundle your code for you ready for web. In this case you'll still need to push this code to the npm registry as a package so others can use it.
The choice between those options is entirely dependent on what it is and who's going to use it and how.
This should be the core requirements in the tscongfig.json file
{
"compilerOptions": {
"lib": ["es6", "es2020.promise", "dom", "es2020"],
"declaration": true,
"target": "es2020",
"module": "es2020"
}
}
While to pack everything in just one file, I think you have to use something like webpack; but I don't know enough this topic to help you on that, sorry.

How to import typescript file from outside of the project folder [duplicate]

Let's say I have two projects with following file structure
/my-projects/
/project-a/
lib.ts
app.ts
tsconfig.json
/project-b/
app.ts // import '../project-a/lib.ts'
tsconfig.json
I want to consume lib.ts located in project-a also from project-b. How to do that?
Release it as NPM module - absolutely don't want that, it's an overkill for such a simple use case. I just
want to share one file between two projects.
Use import '../project-a/lib.ts' - doesn't work, TypeScript complains
'lib.ts' is not under 'rootDir'. 'rootDir' is expected to contain all source files.
Put tsconfig.json one level up so it would cover both project-a and project-b - can't do that, the TypeScript config is slightly different for those projects. Also it's not very convenient, don't want to do that.
Any other ways?
Since Typescript 3.0 this can be done with Project References.
Typescript docs: https://www.typescriptlang.org/docs/handbook/project-references.html
I believe you would have to move lib.ts into a small ts project called something like 'lib'
The lib project should have a tsconfig containing:
// lib/tsconfig.json
{
"compilerOptions": {
/* Truncated compiler options to list only relevant options */
"declaration": true,
"declarationMap": true,
"rootDir": ".",
"composite": true,
},
"references": [] // * Any project that is referenced must itself have a `references` array (which may be empty).
}
Then in both project-a and project-b add the reference to this lib project into your tsconfig
// project-a/ts-config.json
// project-b/ts-config.json
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"moduleResolution": "node"
// ...
},
"references": [
{
"path": "../lib",
// add 'prepend' if you want to include the referenced project in your output file
"prepend": true,
}
]
}
In the lib project. Create a file index.ts which should export all your code you want to share with other projects.
// lib/index.ts
export * from 'lib.ts';
Now, let's say lib/lib.ts looks like this:
// lib/lib.ts
export const log = (message: string) => console.log(message);
You can now import the log function from lib/lib.ts in both project-a and project-b
// project-a/app.ts
// project-b/app.ts
import { log } from '../lib';
log("This is a message");
Before your intelissense will work, you now need to build both your project-a and project-b using:
tsc -b
Which will first build your project references (lib in this case) and then build the current project (project-a or project-b).
The typescript compiler will not look at the actual typescript files from lib. Instead it will only use the typescript declaration files (*.d.ts) generated when building the lib project.
That's why your lib/tsconfig.json file must contain:
"declaration": true,
However, if you navigate to the definition of the log function in project-a/app.ts using F12 key in Visual Studio code, you'll be shown the correct typescript file.
At least, if you have correctly setup your lib/tsconfig.json with:
"declarationMap": true,
I've create a small github repo demonstrating this example of project references with typescript:
https://github.com/thdk/TS3-projects-references-example
This can be achieved with use of 'paths' property of 'CompilerOptions' in tsconfig.json
{
"compilerOptions": {
"paths": {
"#otherProject/*": [
"../otherProject/src/*"
]
}
},
}
Below is a screenshot of folder structure.
Below is content of tsconfig.json which references other ts-project
{
"compilerOptions": {
"baseUrl": "./",
"outDir": "./tsc-out",
"sourceMap": false,
"declaration": false,
"moduleResolution": "node",
"module": "es6",
"target": "es5",
"typeRoots": [
"node_modules/#types"
],
"lib": [
"es2017",
"dom"
],
"paths": {
"#src/*": [ "src/*" ],
"#qc/*": [
"../Pti.Web/ClientApp/src/app/*"
]
}
},
"exclude": [
"node_modules",
"dist",
"tsc-out"
]
}
Below is import statement to reference exports from other project.
import { IntegrationMessageState } from '#qc/shared/states/integration-message.state';
I think that #qqilihq is on the right track with their response - Although there are the noted potential problems with manually maintaining the contents of a node_modules directory.
I've had some luck managing this by using lerna (although there are a number of other similar tools out there, for example yarn workspaces seem to be somewhat similar, although I've not used them myself).
I'll just say upfront that this might be a little heavyweight for what you're talking about, but it does give your project a lot of flexibility to grow in the future.
With this pattern, your code would end up looking something like:
/my-projects/
/common-code/
lib.ts
tsconfig.json
package.json
/project-a/
app.ts (Can import common-code)
tsconfig.json
package.json (with a dependency on common-code)
/project-b/
app.ts (Can import common-code)
tsconfig.json
package.json (with a dependency on common-code)
The general theory here is that the tool creates symlinks between your internal libraries and the node_modules directories of their dependant packages.
The main pitfalls I've encountered doing this are
common-code has to have both a main and types property set in its package.json file
common-code has to be compiled before any of its dependencies can rely on it
common-code has to have declaration set to true in its tsconfig.json
My general experience with this has been pretty positive, as once you've got the basic idea understood, there's very little 'magic' in it, its simply a set of standard node packages that happen to share a directory.
Since I'm the only dev working on 2 projects which require some shared code folder I've setup a 2-way real time sync between the common code shared folders.
project A
- shared/ -> 2-way sync with project B
- abc/
project B
- shared/ -> 2-way sync with project A
- xyz/
It's a one-time quick setup but gives benefits like:
no hassle of configuring and managing mono-repo/multiple tsconfig
no build step to get latest code, works instantly
works with cloud build tools as shared code is inside repo instead of symlink
easier to debug locally as all files are within the project
I can further put checks on shared folder with commit hooks/husky and throw warnings etc.
And in-case if I want to collaborate with other team members, I can mention to use this sync tool as part of project setup.
It seems we've got a few options here:
(1) Put both projects in a mono repo and use answer given by #ThdK using TS project references
NOT GOOD if you don't want a mono repo
(2) Use Lerna - see answer by #metric_caution
NOT GOOD if you can't be bothered to learn Lerna / don't want to publish your shared files to npm
(3) Create a shared npm package
NOT GOOD if you don't want to publish your shared files to npm
(4) Put shared folders in a "shared" directory in project A and write a script to copy files in the shared folder from project A to project B's shared folder that is executed on a git push OR a script to sync the two folders.
Here the script can be executed manually when copying / syncing is required. The copying / syncing could also be done prior to a git push using husky and the shared files added to git automatically in the script.
Since I don't want a mono repo and I can't be bothered to publish an npm package for such a pathetic purpose I'm gonna go with option 4 myself.
In a similar scenario, where I also wanted to avoid the overhead of having to perform an NPM release, I went for the following structure (after lots of trial and error and failed attempts):
/my-projects/
/node_modules/
/my-lib/
lib.ts
tsconfig.json
package.json
/project-a/
app.ts
tsconfig.json
/project-b/
app.ts
tsconfig.json
The central idea is to move the shared stuff to a node_modules directory above the individual projects (this exploits NPMs loading mechanism, which would start looking for dependencies in the current directory and then move upwards).
So, project-a and project-b can now access the lib simply via import { Whatever } from 'my-lib'.
Notes:
In my case, my-lib is actually only for shared typings (i.e. .d.ts files within a lib subdirectory). This means, I do not explicitly need to build my-lib and my my-lib/package.json looks as follows:
{
"name": "my-types",
"version": "0.0.0",
"private": true,
"types": "lib/index"
}
In case my-lib contains runnable code, you’ll obviously need to build my-lib, so that .js files are generated, and add a "main" property to the package.json which exposes the main .js file.
Most important: Despite its name, /my-projects/node_modules only contains custom code, no installed dependencies (they are actually in the individual projects project-a/node_modules and project-b/node_modules). This means, there’s an explicit git ignore setting, which un-ignores the /node_modules directory from being committed.
Is this a clean solution? Probably not not. Did it solve my issue? Yes. Am I happy to hear about improvement suggestions? Absolutely!
I switched to deno. Code sharing in TypeScript projects is easy, at long last.

How can I import third party package without d.ts file?

I'm studying typescript. I get some errors when I tried import some packages. I was check in node_modules folder, it downloaded but this don't have a *.d.ts file. How can I import them?
You can put all your custom imports in my own file. For instance, create shared/types/imports.d.ts file.
declare module "vue-multiselect";
declare module "vue-notification";
And in your tsconfig.json file include those imports with the following lines.
"typeRoots": [
"node_modules/#types", "VueApp/shared/types"
],
And of course, restart your IDE because sometimes it doesn't detect the change immediately.
Make modules for them. Make sure to include the path to your types directory locally:
declare module 'vue-cookie' {
}
All module declarations need to be in their own, separate files. For instance, the vue-cookie file should be named something to the effect of vue-cookie.d.ts.
Also, as you go through the module, you can start typing it correctly.

Is there a way to cause the JS engine to load a .js file without explicitly importing something from it?

Maybe I'm trying to do something silly, but I've got a web application (Angular2+), and I'm trying to build it in an extensible/modular way. In particular, I've got various, well, modules for lack of a better term, that I'd like to be able to include or not, depending on what kind of deployment is desired. These modules include various functionality that is implemented via extending base classes.
To simplify things, imagine there is a GenericModuleDefinition class, and there are two modules - ModuleOne.js and ModuleTwo.js. The first defines a ModuleOneDefinitionClass and instantiate an exported instance ModuleOneDefinition, and then registers it with the ModuleRegistry. The second module does an analogous thing.
(To be clear - it registers the ModuleXXXDefinition object with the ModuleRegistry when the ModuleXXX.js file is run (e.g. because of some other .js file imports one of its exports). If it is not run, then clearly nothing gets registered - and this is the problem I'm having, as I describe below.)
The ModuleRegistry has some methods that will iterate over all the Modules and call their individual methods. In this example, there might be a method called ModuleRegistry.initAllModules(), which then calls the initModule() method on each of the registered Modules.
At startup, my application (say, in index.js) calls ModuleRegistry.initAllModules(). Obviously, because index.js imports the exported ModuleRegistry symbol, this will cause the ModuleRegistry.js code to get pulled in, but since none of the exports from either of the two Module .js files is explicitly referenced, these files will not have been pulled in, and so the ModuleOneDefinition and ModuleTwoDefinition objects will not have been instantiated and registered with the ModuleRegistry - so the call to initAllModules() will be for naught.
Obviously, I could just put meaningless references to each of these ModuleDefinition objects in my index.js, which would force them to be pulled in, so that they were registered by the time I call initAllModules(). But this requires changes to the index.js file depending on whether I want to deploy it with ModuleTwo or without. I was hoping to have the mere existence of the ModuleTwo.js be enough to cause the file to get pulled in and the resulting ModuleTwoDefinition to get registered with the ModuleRegistry.
Is there a standard way to handle this kind of situation? Am I stuck having to edit some global file (either index.js or some other file it references) so that it has information about all the included Modules so that it can then go and load them? Or is there a clever way to cause JavaScript to execute all the .js files in a directory so that merely copying the files it would be enough to get them to load at startup?
a clever way to cause xxJavaScriptxx Node.js to execute all the .js files in a directory:
var fs = require('fs') // node filesystem
var path = require('path') // node path
function hasJsExtension(item) {
return item != 'index.js' && path.extname(item) === '.js'
}
function pathHere(item) {
return path.join('.', item)
}
fs.readdir('./', function(err, list) {
if (err) return err
list.filter(hasJsExtension).map(pathHere).forEach(require) // require them all
})
Angular is pretty different, all the more if it is ng serve who checks if your app needs a module, and if so serves the corresponding js file, at any time needed, not at first load time.
In fact your situation reminds me of C++ with header files Declaration and cpp files with implementation, maybe you just need a defineAllModules function before initAllModules.
Another way could be considering finding out how to exclude those modules from ng-serve, and include them as scripts in your HTML before the others, they would so be defined (if present and so, served), and called by angular if necesary, the only cavehat is the error in the console if one script tag is not fetched, but your app will work anyway, if it supposed to do so.
But anyway, it would be declaring/defining those modules somewhere in ng-serve and also in the HTML.
In your own special case, and not willing to under-evalute ng-serve, but is the total js for your app too heavy to be served at once? (minified and all the ...), since the good-to-go solution may be one of the many tools to build and rebuild your production all.js from your dev js folder at will, or like you said, with a drag&drop in your folder.
Such tool is, again, server-side, but even if you only can push/FTP your javascript, you could use it in your prefered dev environment and just push your new version. To see a list of such tools google 'YourDevEnvironment bundle javascript'.
To do more with angular serve and append static js files under specific conditions, you should use webpack so the first option i see here is eject your webpack configuration and after that you can specify what angular should load or not.
With that said, i will give an example:
With angular cli and ng serve any external javascript files you wanna include, you have to put them inside the scripts array in the angular-cli.json file.However you can not control which file should be included and which one not.
By using webpack configuration you can specify all these thing by passing a flag from your terminal to the webpack config file and do all the process right there.
Example:
var env.commandLineParamater, plugins;
if(env.commandLineParamater == 'production'){
plugins = [
new ScriptsWebpackPlugin({
"name": "scripts",
"sourceMap": true,
"filename": "scripts.bundle.js",
"scripts": [
"D:\\Tutorial\\Angular\\demo-project\\node_moduels\\bootstrap\\dist\\bootstrap.min.js",
"D:\\Tutorial\\Angular\\demo-project\\node_moduels\\jquery\\dist\\jquery.min.js"
],
"basePath": "D:\\Tutorial\\Angular\\demo-project"
}),
]}else{
plugins = [
new ScriptsWebpackPlugin({
"name": "scripts",
"sourceMap": true,
"filename": "scripts.bundle.js",
"scripts": [
"D:\\Tutorial\\Angular\\demo-project\\node_moduels\\bootstrap\\dist\\bootstrap.min.js"
],
"basePath": "D:\\Tutorial\\Angular\\demo-project"
}),
]
}
then:
module.exports = (env) => {
"plugins": plugins,
// other webpack configuration
}
The script.js bundle will be loaded before your main app bundle and so you can control what you load when you run npm run start instead of ng-serve.
To Eject your webpack configuration, use ng eject.
Generally speaking, when you need to control some of angular ng-serve working, you should extract your own webpack config and customize it as you want.

How to publish a library of Vue.js components?

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.

Categories