webpack: transpile web workers in public folder - javascript

I'm next js for my project and it uses webpack 5 to compile typescript codes
I have several web worker scripts inside my public folder under path "/workers/**/*.worker.js"
I was wondering if I can write them in typescript too
or at least use babel to transpile them for es5 (for old browsers)
I know that anything under the "public" folder is served as is and as a file (like a CDN)
can I add a "workers" folder to my project and load them in the public path with webpack and next js?

thanks to #nalin-ranjan I've come up with the solution
in my "next.config.js" I added a rule to my webpack config:
module.exports = {
webpack: (config) => {
config.module.rules.push({
test: /\.worker\.ts$/,
type: 'asset/resource',
generator: {
filename: 'static/[hash:5].[name].js',
},
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
configFile: __dirname + '/worker.tsconfig.json',
},
},
],
})
return config
},
}
with this rule, I could require my workers and use them as URLs and transpile typescript
also, I had to add a new "tsconfig" that had "isolatedModules" option disabled (because web workers are not modules). for that, I created an identical tsconfig file to next.js but disabled isolatedModules. the reason being that next.js forbids you to disable isolatedModules and resets it back

Related

Use webpack plugins without 'entry' index.js

I'm using Webpack to assemble dist directories with environment-specific configs (manifest.json) and file structures
My issue is that webpack wants me to have an empty src/index.js file
In certain environments, the compilation happens after I've assembled the /dist folder, zipped, and uploaded to their service.
Is there any way to avoid index.js and just run CopyWebpackPlugin?
I'm currently looking into using manifest.json or manifest.js as the entry for this build, since this file is the connection between an options HTML app, a browser content script, icon.png, etc
Both CleanWebpackPlugin and HtmlWebpackPlugin have given me tons of problems, and it seems that using loaders may be the correct, pure, synchronous way to go
My current inspiration uses a simple module.exports with arguments[0] source from the previous loader to easily transform the source
I did come up with a hack (un-maintanable) solution to delete the unused file afterwards, which may be of use to someone.
webpack.config.js
module.exports = {
name: 'manifest',
entry: {
manifest: './src/manifest.json',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'DELETED.js',
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{from: './src/manifest.json'},
{from: './assets/images/icon.png'},
],
}),
new RemoveFilesWebpackPlugin({
before: {
log: false,
include: [
'dist',
],
},
after: {
log: false,
include: [
'dist/DELETED.js',
],
},
}),
],
mode: 'none',
}
The problem with this idea is that manifest.json is not being used to gather it's own assets -- the assets are declared in this bundler's config.
I have two additional webpack.config.js files for
generating an HTML options page using HtmlWebpackPlugin
bundling the main script
but as mentioned, maintenance requires digging in the build file, rather than just editing source and requiring relative paths

Dynamic loading of external modules in webpack fails

I am trying to set up the following architecture: a core React application that gets built with some basic functionality, and the ability to load additional React components at runtime. These additional React components can be loaded on-demand, and they are not available at build time for the core application (so they cannot be included in the bundles for the core application, and must be built separately). After researching for some time, I came across Webpack Externals, which seemed like a good fit. I am now building my modules separately using the following webpack.config.js:
const path = require('path');
const fs = require('fs');
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
module.exports = {
entry: './src/MyModule.jsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'MyModule.js',
library: 'MyModule',
libraryTarget: 'umd'
},
externals: {
"react": "react",
"semantic-ui-react": "semantic-ui-react"
},
module: {
rules: [
{
test: /\.(js|jsx|mjs)$/,
include: resolveApp('src'),
loader: require.resolve('babel-loader'),
options: {
compact: true,
},
}
]
},
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx']
}
};
Took a look at the generated MyModule.js file, and it looks correct to me.
Now, in my core app, I am importing the module as follows:
let myComponent = React.lazy(() => import(componentName + '.js'));
where componentName is the variable that matches the name of my module, in this case, "MyModule" The name is not known at build time, and the file is not present in the src folder at build time. To avoid errors from webpack when building this code with an unknown import, I have added the following to my webpack.config.js for the core project:
module.exports = {
externals: function (context, request, callback/*(err, result)*/) {
if (request === './MyModule.js') {
callback(null, "umd " + request);
} else {
callback();
}
}
}
I have confirmed that the function in externals gets called during the build, and the if condition is matched for this module. The build succeeds, and I am able to run my core application.
Then, to test dynamic loading, I drop MyModule.js into the static/js folder where the bundles for my core app live, then I navigate to the page in my core app that requests MyModule via let myComponent = React.lazy(() => import(componentName + '.js'));
I see a runtime error in the console on the import line,
TypeError: undefined is not a function
at Array.map (<anonymous>)
at webpackAsyncContext
My guess is it's failing to find the module. I don't understand where it is looking for the module, or how to get more information to debug.
Turns out that I was making a couple of incorrect assumptions about webpack and dynamic loading.
I was having issues with two things - the kind of module I was loading, and the way that I was loading it.
Dynamic importing is not yet a standard ES feature - it is due to be standardized in ES 2020. This dynamic import will only return a module if the module object you are attempting to load is an ES6 module (aka something that contains an 'export ModuleName'). If you attempt to load something packed up as a CommonJS module, AMD, UMD, the import will succeed, but you will get an empty object. Webpack does not appear to support creating bundles in ES6 format - it can create a variety of module types, and in my config file above, I was actually creating UMD modules (configured via libraryTarget setting).
I had issues with the import statement itself because I was using it within an app bundled by Webpack. Webpack reinterprets the standard ES import statement. Within a standard webpack config (including the one you get from CRA), webpack uses this statement as a split point for bundles, so even modules that are dynamically imported are expected to be there at webpack build time (and the build process will fail if they are not available). I had tried to use webpack externals to tell webpack to load the modules dynamically, which allowed the build to succeed without the modules being there. However, the app still used Webpack's import function instead of the standard JS import function at runtime. I confirmed this by attempting to run import('modulename') from the browser console and getting a different result than my app, which was bundled with webpack.
To solve problem #2, you can tell Webpack to not reinterpret the ES dynamic import by adding some annotation to the import statement.
import(/*webpackIgnore: true*/ 'path/to/module.js');
This will both prevent Webpack from attempting to find and bundle the dynamically imported module at build time, and attempting to import it at runtime. This will make behavior in the app match behavior in the browser console.
Problem #1 was a bit more difficult to solve. As I mentioned above, importing a non-ES6 module will return an empty object (if you await the promise or use .then()). However, as it turns out, the file itself does load and the code gets executed. You can export the module in the "window" format using Webpack, and then load it as follows.
await import(/*webpackIgnore: true*/`path/to/module.js`);
let myModule = window['module'].default;
Another potential solution that avoids using the window object is building the module using a system capable of producing ES6 modules (so, not Webpack). I ended up using Rollup to create an ES6 module that pulled all dependencies into a single file, and ran the output through Babel. This produced a module that loaded successfully via a dynamic ES import. The following was my rollup.config.js (note that I included all external node modules needed in my module - this bloated the module size but is a requirement for my specific application - yours will likely differ and you will need to configure rollup to exclude the modules)
// node-resolve will resolve all the node dependencies
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'rollup-plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import replace from 'rollup-plugin-replace';
export default {
input: 'src/myModule.jsx',
output: {
file: 'dist/bundle.js',
format: 'esm'
},
plugins: [
resolve(),
babel({
exclude: 'node_modules/**'
}),
commonjs({
include: 'node_modules/**',
namedExports: {
'node_modules/react/index.js': ['Children', 'Component', 'PropTypes', 'PureComponent', 'React', 'createElement', 'createRef', 'isValidElement', 'cloneElement', 'Fragment'],
'node_modules/react-dom/index.js': ['render', 'createElement', 'findDOMNode', 'createPortal'],
'node_modules/react-is/index.js': ['isForwardRef']
}
}),
replace({
'process.env.NODE_ENV': JSON.stringify( 'production' )
})
]
}

webpack load AMD modules without bundling

I'm migrating a web app from requireJS to webpack.
With requireJS, I have different configurations depending on the environment.
For live environment I use r.js to minify and bundle all of my
modules and their dependencies into a single file. Afterwards, I add
almondJS to manage the dependencies and then I load my js bundle like the following:
<script src="bundle.min.js"></script>
For my development environment, I Load requireJS like this:
<script src="require.js" data-main="/main-config"></script>
and requireJS will use my configuration file specified by data-main, to load modules and their
dependencies asynchronously
As you can see, with requireJS module loading and bundling are two separate processes and that allows me to debug AMD modules during development without needing sourcemaps
How can I achieve this scenario using webpack as a module loader only without bundling during development ?
If this is not possible, is there any other way I can see my source files in the browser debugger without generating sourcemaps?
How can I achieve this scenario using webpack as a module loader only without bundling during development ?
Webpack will always bundle, despite the envieronment.
If this is not possible, is there any other way I can see my source files in the browser debugger without generating sourcemaps?
If your code is transpiled/compiled, you'll need sourcemaps to see that. There is no way to workaround that.
It's true that if your code is transpiled then you'll need sourcemaps. But it is possible to get around bundling though. Yes, webpack will really always try to bundle, but with plugins the code can be taken out of the bundle and placed in the output directory as if it was simply run through the transpiler.
I have a node application that I want to simply transpile to ES5 file-by-file and not bundle anything. So my config to do that is roughly this:
let config = {
entry: [
glob.sync(srcDir + '/**/*.js') // get all .js files from the source dir
],
output : {
filename : '[name].rem.js', // webpack wants to bundle - it can bundle here ;)
path: outDir
},
resolve: {
alias: {
'app': appDir
}
},
plugins: [
new RemoveEmptyScriptsPlugin({extensions: ['js'], scriptExtensions: /\.rem\.js/}) // for all .js source files that get bundled remove the bundle .rem.js file
],
module: {
rules:[{
test: /\.jsx?$/,
type: 'asset/resource', // get webpack to take it out instead of bundling
generator: {
filename: ({filename}) => filename // return full file name so directory structure is preserved
},
use: {
loader: 'babel-loader',
options: {
targets: { node: 16 },
presets: [
['#babel/preset-env', { modules: 'commonjs' /* transpile import/export */}],
]
}
}
}]
}
};
// Since the code does not go through the full pipeline and imports are not getting resolved, aliases will remain in the code.
// To resolve them it takes to hack the aliases object into the babel config
config.module.rules[0].use.options.plugins.push(['babel-plugin-webpack-alias-7', {config: {resolve: {alias: config.resolve.alias}}}];
But then it appeared that the currently published babel-plugin-webpack-alias-7 does not support providing an Object to the config option so I had to patch the plugin https://github.com/shortminds/babel-plugin-webpack-alias-7/pull/22
Ah, and then the webpack-remove-empty-scripts plugin had an issue with my idea so I had to patch that too https://github.com/webdiscus/webpack-remove-empty-scripts/pull/6

javascript + gulp + babel + webpack. error: Module not found: Error: Can't resolve 'babel-loader'

I'm creating a javascript project. To create it I'm using gulp and babel.
My problem is that I can't develop my code over multiple file, so I'm search a solution to 'enable' importing. At the moment I'm trying to configure webpack.
The Gulp Task is this:
gulp.task('webpack', () => {
return webpack_stream(webpack_config)
.pipe(rename('webpack_code.js'))
.pipe(gulp.dest('.build/asset/webpack/'));
});
The webpack.config.js is this:
module.exports = {
entry: ['./src/asset/js/main.js'],
output: {
filename: 'bundle.js',
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js)$/,
exclude: /(node_modules)/,
loader: 'babel-loader',
query: {
presets: [
['env', 'stage-0',{ modules: false }],
],
},
},
],
},
resolveLoader: {
modules: ['./node_modules'],
},
resolve: {
modules: ['./node_modules'],
},
target: 'node',
};
My current error is this:
Error in plugin 'webpack-stream'
Message:
multi ./src/asset/js/main.js
Module not found: Error: Can't resolve 'babel-loader' in ...
What's wrong?
Another Question: What's I have to put as value of entry key? Only the entry point js file or the whole files of the project?
Thanks!!
What's wrong?
I'd guess that in your project, your Webpack instance is not finding the babel loader because of your config / environment specific issues.
I've had the exact same issue as you. Here are some troubleshooting steps for to check first:
See if babel-loader is actually installed. I know it is simple, but it can save you time.
Check which Webpack/Babel versions you're dealling with in your package.json file. I'm using Webpack 4 and Babel 8. Sounds like some newer versions doesn't accept this: use: 'babel' in your webpack.config file. You need to ensure that the -loader is being used as it follows: use: 'babel-loader'.
Reinstall your node_modules folder. Sometimes it works.
Another Question:
What's I have to put as value of entry key?
Only the entry point js file or the whole files of the project?
Accordingly to Webpack's docs:
The entry object is where webpack looks to start building the bundle. The context is an absolute string to the directory that contains the entry files. - Webpack Ref
Considering that, you should pass to the entry object, the path of a folder or a file that will be used to generate your final JS file with all your modules in it.
If you have nested files, that you don't import as modules, I think you'll have to head to the docs and see this specific case.
But if this files are nested and are being imported as modules, in your entry file/folder, they will be generated in the output file.
I know it's not much but following these steps, helped me to solve it. :)

Importing UMD style module with ES6, Webpack, and Babel

I have found a few StackOverflow questions related to this but none that match nor fix my problem.
I am writing a library in ES6 that is intended to be used in the browser and on the server. I have found a few HTTP request libraries that can be use on the server or browser (popsicle, axios). I have successfully used these libraries in both places but am having some issues when importing them in my source and using the outputted webpacked file.
My ES6 source file where I am importing the axios library is
import axios from 'axios';
export default {
go: function() {
return axios.get('http://www.google.com');
}
};
My webpack config is
var webpack = require('webpack');
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
var WebpackNotifierPlugin = require('webpack-notifier');
var path = require('path');
var env = require('yargs').argv.mode;
var libraryName = 'library';
var source = '/src/test.js';
var plugins = [],
outputFile;
if (env === 'build') {
plugins.push(new UglifyJsPlugin({
minimize: true
}));
outputFile = libraryName + '.min.js';
} else {
outputFile = libraryName + '.js';
plugins.push(new WebpackNotifierPlugin())
}
var config = {
entry: __dirname + source,
devtool: 'source-map',
output: {
path: __dirname + '/lib',
filename: outputFile,
library: libraryName,
libraryTarget: 'umd',
umdNamedDefine: true
},
externals: {},
module: {
loaders: [{
test: /(\.jsx|\.js)$/,
loader: 'babel',
exclude: /(node_modules|bower_components)/
}, {
test: /(\.jsx|\.js)$/,
loader: "eslint-loader",
exclude: /node_modules/
}]
},
resolve: {
root: path.resolve('./src'),
extensions: ['', '.js']
},
plugins: plugins
};
module.exports = config;
As you can see I am setting the libraryTarget to umd and umdNamedDefine to true
And my .bablerc is
{
"presets": ["es2015"],
"plugins": ["add-module-exports", "babel-plugin-add-module-exports"]
}
I am able use my library in the browser by including it in a script tag but I am not able to use it when importing with node. I get an XMLHttpRequest is not defined. The axios library says it uses XMLHttpRequest when on the browser and http when running in node but for some reason it is not detecting it is being run on the server. I am having this issues with a few UML libraries so believe it is not the specific package. Also, since I can use these libraries in node ES5 without running webpack or babel I am led to assume it is a configuration issue with webpack.
How can I import these UMD style libraries in ES6 and generate a library with Webpack and Babel that can be used on the server and browser?
To make your package as small as possible I'd recommend using the Fetch API. A UMD library has the three types of consumers where fetch comes in handy;
Node.js - has not implemented but can use node-fetch to polyfill
common behaviour using only node libraries (no heavy dependencies
like superagent, unirest and axios etc - these add security concerns
as well as bloat!).
Browser - Fetch is a WHATWG standard and is
available in most modern browsers but might require an npm package
such as whatwg-fetch to polyfill older browsers
Isomorphic/Universal - the same javascript running in browser and
node.js which you find in progressive web apps.They need to use a
library called isomorphic-fetch to load either whatwg-fetch or the
node.js version of fetch.
It should be handled by the projects consumer though so README should include instructions to each of the three types of users above.
Node.js and isomorphic instructions are basically as below.
require(‘node-fetch’); // or require(‘isomorphic-fetch’)
const MyUMDLibrary = require('my-umd-library');
const myUMDLibrary = new MyUMDLibrary();
For browsers using the script from cdn they could also load a fetch polyfill before https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.js.
I'd suggest using rollup for a UMD library (see a working example I've contributed before for a UMD library) but Webpack shouldn't be a problem either. Rather than trying to include the 'axios' dependency within your application with Fetch you can leave it out and allow your users to decide how they will load the package (i.e. module loader, script, require).

Categories

Resources