I'm writing a library using handlebars templates and I want to use Webpack to bundle it.
I'm using handlebars-loader so that I can require and precompile the templates.
However I don't want handlebars (nor handlebars/runtime) to be included in my compiled library and thus, I would like to set them as externals.
Here is my config file:
module.exports = {
context: __dirname + '/src',
entry: './index.js',
output: {
path: __dirname + '/dist',
filename: 'stuff.js',
libraryTarget: 'umd',
library: 'Stuff'
},
externals: [{
'handlebars/runtime': {
root: 'Handlebars',
amd: 'handlebars.runtime',
commonjs2: 'handlebars/runtime',
commonjs: 'handlebars/runtime'
}
}],
module: {
loaders: [
{ test: /\.handlebars$/, loader: 'handlebars-loader' }
]
}
};
Unfortunately it doesn't work and handlebars-loader still makes handlebars/runtime to be bundled...
I believe it is because I do not require handlebars/runtime directly, rather it is required in the code added by the loader.
Is there a way to mark it as external?
Edit: I know I need handlebars/runtime to compile my template. But as I am building a library, I want it to be provided by the user of the library instead of being included. This way, if my user is also using Handlebars, the library is not loaded twice.
I believe it is good practice for libraries to avoid bundling any dependency (it is something we see much too often in my humble opinion).
The handlebars-loader's team helped me solve this issue.
The problem is that handlebars-loader, by default, loads handlebars' runtime using an absolute path.
However, it is possible to use the runtime argument of the handlebar loader to specify a different path for handlebars' runtime. It is then possible to set this path as external.
This is working:
module.exports = {
context: __dirname + '/src',
entry: './index.js',
output: {
path: __dirname + '/dist',
filename: 'stuff.js',
libraryTarget: 'umd',
library: 'Stuff'
},
externals: [{
'handlebars/runtime': {
root: 'Handlebars',
amd: 'handlebars/runtime',
commonjs2: 'handlebars/runtime',
commonjs: 'handlebars/runtime'
}
}],
module: {
loaders: [
{ test: /\.handlebars$/, loader: 'handlebars-loader?runtime=handlebars/runtime' }
]
}
};
Related
I have a TypeScript project (Project A) which uses Webpack to create a bundle with target node.
This application needs to consume a Javascript file which is the output bundle of another project (Project B). This project emits, always via Webpack, a Javascript library for the browser with target web.
However, Project A needs to import this Javascript file raw, just as a string. It needs the content of the file, it does not need to access its types and functions.
Project structure
The webpack.config.js in Project A is as follows>
const path = require("path");
module.exports = {
target: "node",
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
{
test: /\.js$/,
use: "raw-loader",
},
],
},
resolve: {
alias: {
Jsfile$: path.resolve(__dirname, "../projb/dist/bundle.js"),
},
extensions: [".ts", ".js", "..."],
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
library: {
name: "bundle",
type: "umd",
},
globalObject: "this",
},
};
Project B build script instructs Webpack to create a bundle with target web to be placed inside projb/dist/bundle.js.
As you can see, I try to use the raw-loader in Project A and I use an alias.
The problem
The file I have in Project A which is trying to import the JS file is:
import jsfile from "Jsfile";
But it is not working, giving me error:
ERROR in C:\Users\myuser\Documents\myproj\proja\src\file_importer.ts
./src/file_importer.ts 3:24-34
[tsl] ERROR in C:\Users\myuser\Documents\myproj\proja\src\file_importer.ts(3,25)
TS2307: Cannot find module 'Jsfile' or its corresponding type declarations.
What am I doing wrong?
Webpack describes a multi-main entry feature that seems to do exactly this, it bundles several libs into one file. The problem is that when I load that file only the last library on the list is available.
I've created a small demo on github.
There are 2 libraries each exporting a single symbol, only lib2.d2 is accessible from the test.html that loads the bundled JS. If you look in the bundle file you can see the code from lib1 but it's not exported in any way that I can find.
The webpack config is below. I suspect the problem is that there's no way to supply 2 library names when there's out only output and so the last one over-writes the earlier ones.
const path = require('path');
module.exports = {
entry: ['./js/lib1.js', './js/lib2.js'],
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
library: {
name: 'MyLibrary',
type: 'umd',
},
filename: 'lib.js',
path: path.resolve(__dirname, 'dist'),
},
"optimization": {
"minimize": false,
usedExports: true,
},
mode: "development",
};
If you're curious why I would want to do this, I'm doing some development on wix.com. The only way to push JS files up to their server is copy/paste one at a time. Bundling a bunch of stuff into one file will save me some pain. My current work-around is to output to multiple files and then cat them all together with something export const exportLib1 = lib1 for each one. That gives me a JS file that I can import and access each one but there must be an easier way.
I am still in the process of setting up my project configuration so I don't have any errors to work with right now, but if I am understanding the Typescript docs correctly...
It seems like Project references TypeScript Docs - Project references are not that necessary if transpiling with babel-loader in webpack. (I'm working in VSCode)
I am trying to convert an Electron app to TypeScript and currently reorganizing the folder structure so I have minimal issues.
I am trying to understand if I am on the right track and if I can avoid including "references" and instead just use "extends" to get the functionality I want.🤔
Here is my project structure ignoring all files that are not tsconfig files:
./tsconfig.json
./tsconfig-base.json
./main/tsconfig.json
./src/client/tsconfig.json
./__tests__
./__tests__/__client__/tsconfig.json
./__tests__/__main__/tsconfig.json
In this structure ./tsconfig.json would really just be for references like the example on Microsoft's Github
Electron Main Process and related files are in ./main. The tsconfig here will set "module":"commonjs" for working in node. I think it will also extend from the ./tsconfig-base.json
Electron Renderer Process and my React-Redux app files are in ./src/client. The tsconfig here sets "module":"es2015" or "module":"ESNEXT" for working with es modules. I think it will also extend from the ./tsconfig-base.json
The ./__tests__/__client__/tsconfig.json and ./__tests__/__main__/tsconfig.json would just be duplicates of the non tests folder versions similarly extending from the base config in ./
Webpack config is already set up to handle creating separate bundles for main and renderer processes so that the entire app can be in TypeScript. Is there any reason I should be using "references" in my files in the main or client folders?
Sample snippets of the Webpack config before I switch the tnry files to be .ts files (dev):
const rendererInclude = path.resolve(__dirname, "src");
const mainInclude = path.resolve(__dirname, "main");
Main Process:
module.exports = [
{
mode: "development",
entry: path.join(__dirname, "main", "swell.js"),
output: {
path: path.join(__dirname, "dist"),
filename: "main-bundle.js",
},
target: "electron-main",
node: {
__dirname: false,
__filename: false,
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
loader: "babel-loader",
include: mainInclude,
exclude: /node_modules/,
}
] } ... },
continued to Renderer Process:
{
mode: "development",
entry: path.join(__dirname, "src", "index.js"),
output: {
path: path.join(__dirname, "dist"),
filename: "renderer-bundle.js",
},
target: "electron-renderer",
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
loader: "babel-loader",
include: rendererInclude,
exclude: /node_modules/,
]} ...} ]
Project Reference will help you solve some problems in case where your test projects import src/main modules.
This is what Project Reference solves:
There’s no built-in up-to-date checking, so you end up always running tsc twice
Invoking tsc twice incurs more startup time overhead
tsc -w can’t run on multiple config files at once
read more about Project References
I'm developing a npm module and I would like it to be importable by all kind of js client side app.
Right now I tried export default myObject and module.exports = myObject
The problem is export default seems to be available only in es6 app and module.exports doesn't work in pure javascript as module is not defined.
So I would like my module to be accessible if the client use React, Angular, Vue, pure Javascript or whatever... Also my module is just an object with a list of pure javascript functions inside. No tricky part here.
Is there a way to ensure that the module is available regardless of the technology the client will use ?
The subject is a bit old now but just in case someone get the same problem.
I got it working be using UMD as Yury suggested. After some unsuccessful tries I ended up using webpack directly. Never knew he was providing us these great tools to get UMD so simply. Here is my configuration file.
I export two configuration to build a normal and a minified version at the same time.
module.exports = [
{
entry: path.resolve(__dirname, "src/myLib.js"),
output: {
path: path.resolve(__dirname, "dist"),
filename: "myLib.js",
library: 'myLib',
libraryTarget: "umd",
umdNamedDefine: true,
},
mode: "development",
module: config.module,
resolve: config.resolve,
plugins: config.plugins,
},
{
entry: path.resolve(__dirname, "src/myLib.js"),
output: {
path: path.resolve(__dirname, "dist"),
filename: "myLib.min.js",
library: 'myLib',
libraryTarget: "umd",
umdNamedDefine: true,
},
mode: "production",
module: config.module,
resolve: config.resolve,
plugins: config.plugins,
},
];
I was reading this webpack tutorial:
https://webpack.github.io/docs/usage.html
It says it bundles the src files and node_modules. If I want to add another .js file there, how can I do this? This is a thirdpartyjs file that is not part of the source and not part of the node_modules files. This is my current webpack.config.js:
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./app/app.js'
],
output: {
path: path.resolve(__dirname, "dist"),
publicPath: "/dist/",
filename: "dist.js",
sourceMapFilename: "dist.map"
},
devtool: 'source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development')
}
}),
],
module: {
loaders: [{
loader: 'babel',
exclude: /node_modules/
}]
},
devServer: {
inline: true
},
node: {
fs: "empty"
},
watch: false
}
The start point for code is the entry field in config. In your config entry point is the list of files. Webpack gets all, resolve their dependencies and output in one file.
You have two options for adding third party script:
add the file path to entry list before app.js
require this file from app.js
In response to Dmitry's answer:
add the file path to entry list before app.js
This has the effect that you will get a bundled .js file for each entry point, which you might not want.
require this file from app.js
You might not have access to app.js if it is written dynamically, or for whatever reason you might not want to edit app.js.
Another option:
You can use webpack-inject-plugin to inject any JS code as string into the resulting .js bundle created by webpack. This way you can read the File you want to inject as a string (e.g. fs.readFile in nodejs) and inject it with the plugin.
Another solution but without using any extra plugins:
//Webpack.config.js
entry: {
main: './src/index',
/**
/* object is passed to load script at global scope and exec immediately
/* but if you don't need then simply do:
/* myCustomScriptEntry: './src/myCustomScript'
*/
myCustomScriptEntry: {
import: './src/myCustomScript',
library: {
name: 'myCustomScriptEntry',
type: 'var',
},
},
},
new HtmlWebpackPlugin({
template: './public/index.html',
excludeChunks: ['myCustomScriptEntry'], //exclude it from being autoreferenced in script tag
favicon: './public/favicon.svg',
title: 'Alida',
}),
and
//index.html
<script type="text/javascript" src="<%= compilation.namedChunks.get('myCustomScriptEntry').files[0] %>"></script>