Browserify, Babel 6, Gulp - Unexpected token on spread operator - javascript

I'm trying to get my Browserify/Babelify/Gulp working in my project, but it won't take the spread operator.
I got this error from my gulpfile:
[SyntaxError: /Users/mboutin2/Desktop/Todo-tutorial/src/reducers/grocery-list-reducers.js: Unexpected token (16:8) while parsing file: /Users/mboutin2/Desktop/Todo-tutorial/src/reducers/grocery-list-reducers.js]
This is my gulpfile.js
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var browserify = require('browserify');
var sourcemaps = require('gulp-sourcemaps');
var uglify = require('gulp-uglify');
var buffer = require('vinyl-buffer');
var babelify = require('babelify');
gulp.task('build', function () {
return browserify({entries: './src/client/app.js', extensions: ['.js'], debug: true})
.transform(babelify, {presets: ['es2015', 'react']})
.bundle()
.on('error', function (err) {
console.error(err);
this.emit('end');
})
.pipe(source('app.min.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(uglify())
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./public/js'));
});
gulp.task('default', ['build']);
I tried to create a .babelrc file, but it do the same thing. And my script works when i delete the spread operator.
This is the file where the Unexpected token occurs (quite simple).
import utils from '../utils/consts';
const initialState = {
itemList: [
{name: 'Apple', type: 'Fruit'},
{name: 'Beef', type: 'Meat'}
]
};
export function groceryList(state = initialState, action = {}) {
switch(action.type) {
case utils.ACTIONS.ITEM_SUBMIT:
return {
...state,
itemList: [
...state.itemList,
{name: action.name, type: action.itemType}
]
};
default:
return state;
}
}
I don't know what doesn't work in this, i read some issues on Github and the setup page on Babel website, but i can't make it work correctly.
Can anyone show me how to handle this correctly? Thank you

That syntax is an experimental proposed syntax for the future, it is not part of es2015 or react so you'll need to enable it.
npm install --save-dev babel-plugin-transform-object-rest-spread
and add
"plugins": ["transform-object-rest-spread"]
into .babelrc alongside your existing presets.
Alternatively:
npm install --save-dev babel-preset-stage-3
and use stage-3 in your presets to enable all stage-3 experimental functionality.

I had the same issue, installed stage-2 plugin and modified my package.json file, which looks like below
"babel": {
"presets": [
"es2015",
"react",
"stage-2"
]
}

Just as a side note, some text editors (in my case Mac Notes) will contract ... into an elepsis entity, which will fail validation, so be aware of that too...

Related

How to integrate es6 with gulp-develop-server

I am trying to transfer an old node-express project over to be able to use es6. I have seen many posts about using gulp with es6. Most of them discuss using a syntax like this:
const gulp = require("gulp");
const babel = require("gulp-babel");
gulp.src('./index.js')
.pipe(
babel({
presets: [
["#babel/env", { modules: false }],
],
})
)
However my existing project's gulpfile does't use gulp.src at all. Instead, it uses gulp-develop-server. The gulpfile looks like this:
const gulp = require("gulp");
const devServer = require("gulp-develop-server");
const spawn = require("child_process").spawn;
const fs = require("fs");
const basedir = ".";
function serverRestart(done) {
// perform some cleanup code here
devServer.restart();
done();
}
function serverStart() {
devServer.listen({
path: basedir + "/index.js",
});
}
function serverWatch() {
serverStart();
gulp.watch(
[
basedir + "/paths/**/*",
// more directories to watch
],
serverRestart
);
}
function reload(done) {
serverWatch();
done();
}
function defaultTask() {
let p;
gulp.watch(["gulpfile.js"], killProcess);
spawnChild();
function killProcess(e) {
if (p && !p.killed) {
devServer.kill();
p.kill("SIGINT");
spawnChild();
}
}
function spawnChild() {
p = spawn("gulp", ["reload"], { stdio: "inherit" });
}
}
process.stdin.resume();
process.on("exit", handleExit.bind(null, { cleanup: true }));
process.on("SIGINT", handleExit.bind(null, { exit: true }));
process.on("uncaughtException", handleExit.bind(null, { exit: true }));
function handleExit(options, err) {
// perform some cleanup code here
if (options.cleanup) {
devServer.kill();
}
if (err) {
console.log(err.stack);
}
if (options.exit) {
process.exit();
}
}
gulp.task("serverRestart", serverRestart);
gulp.task("serverStart", serverStart);
gulp.task("serverWatch", serverWatch);
gulp.task("reload", reload);
gulp.task("default", defaultTask);
The existing flow is important because it executes needed code for setup and cleanup every time I hit save, which runs serverRestart. I've been trying a few different methods based on the other questions which recommended using gulp.src().pipe(), but I havne't had much luck integrating it with the existing pattern which uses gulp-develop-server. I am trying to not have to rewrite the whole gulpfile. Is there a simple way to integrate babel with my existing gulpfile such that I can use es6 in my source code?
There's an example with CoffeeScript in the gulp-develop-server documentation.
Using that as a model, try this:
function serverStart() {
devServer.listen({
path: "./dist/index.js",
});
}
function serverWatch() {
serverStart();
gulp.watch(
[
basedir + "/paths/**/*",
],
serverRestart
);
}
function serverRestart() {
gulp.src('./index.js')
.pipe(
babel({
presets: [
["#babel/env", { modules: false }],
],
})
)
.pipe( gulp.dest( './dist' ) )
.pipe( devServer() );
}
Other suggestions
That being said, your existing Gulp file doesn't actually really use Gulp. That is, everything is defined as a function and it doesn't leverage any of Gulp's useful features, like managing task dependencies. This is because (pre-es6), this was a very simple project. The Gulp tasks in that file are an over-elaborate way to watch files and run a server. The same could be done (with less code) using nodemon.
With the introduction of React and more complicated build processes, Gulp seems to have fallen out of favor with the community (and in my personal experience, Gulp was a time sinkhole anyhow).
If the main change you want to make is to use import, you can simply use a more recent Node version. You'll surely run into the error SyntaxError: Cannot use import statement outside a module. Simply rename the file to .mjs and it will work. This provides a way to incrementally migrate files to import syntax. Other features should automatically work (and are all backwards-compatible, anyhow). Once your project is mostly, or all, compliant, you can add "type": "module" to your package.json file, then rename all of your require-style js files to .cjs, and rename all of your .mjs files to .js, or leave them as .mjs. Read more about the rules of mixing CommonJS and Module imports in the Node.js blog post (note that some things may have changed since that article was written).

Connecting files via import does not work

For a day now, I can not understand why babel for gulp does not work for me, or I incorrectly connect the modules ...
The error in the browser is like this:
Uncaught ReferenceError: require is not defined
at main.min.js:1
I connect the modules like this:
import focusVisible from "focus-visible";
Code in main.min.js file:
"use strict";var e;(e=require("focus-visible"))&&e.__esModule;
Gulp task:
const { src, dest, series, watch, parallel } = require('gulp'),
fileinclude = require('gulp-file-include'),
rename = require("gulp-rename"),
uglify = require('gulp-uglify-es').default,
babel = require("gulp-babel"),
notify = require("gulp-notify"),
browserSync = require("browser-sync").create()
const changingScripts = () => {
return src(['src/js/main.js', 'src/js/pages/**/*.js'])
.pipe(babel())
.pipe(fileinclude())
.pipe(dest('dist/js'))
.pipe(uglify({
toplevel: true
}).on('error', notify.onError()))
.pipe(rename({
extname: '.min.js'
}))
.pipe(dest('dist/js'))
.pipe(browserSync.stream())
}
The package.json file is like this:
{
"name": "project_name",
"version": "1.0.0",
"description": "some description of the project",
"scripts": {},
"keywords": ["keyword_1", "keyword_2", "keyword_3"],
"author": "project_author",
"license": "ISC",
"devDependencies": {
"#babel/core": "^7.16.0",
"#babel/eslint-parser": "^7.16.3",
"#babel/preset-env": "^7.16.4",
"#babel/register": "^7.16.0",
"browser-sync": "^2.27.7",
"eslint": "^8.2.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.3",
"gulp": "^4.0.2",
"gulp-babel": "^8.0.0",
"gulp-file-include": "^2.3.0",
"gulp-notify": "^4.0.0",
"gulp-rename": "^2.0.0",
"gulp-uglify-es": "^3.0.0"
}
}
The .babelrc file looks like this:
{
"presets": ["#babel/preset-env"]
}
It seems like everything has applied what is needed.
If you can help, I will be grateful.
The whole project got stuck because of this error ...
It is advisable to solve the problem without using Webpack :)
It seems like you could be missing a build step where you transform your code to be browser compatible. The require method is not available in the browser.
You have to use a tool which transforms your code so that it can be ran in the browser. One such tool is Browserify, another is rollup, and there may be more. These tools, generally speaking, bundle dependency sources in conjunction with your application code, which allows require statements to be transformed into some other pattern the browser does understand.
You can think of it like this (example is simplified):
Code written by you
// main.js
const stringify = require('stringify')
alert(stringify({ error: "No authorization" })
Dependency source in node_modules
// node_modules/stringify/index.js
function stringify(obj) {
return JSON.stringify(obj);
}
expost.default = stringify
Bundle result
// dist/main.js
function stringify(obj) {
return JSON.stringify(obj);
}
alert(stringify({ error: "No authorization" })
Gulp hosts an official example of usage in their repository for browserify:
'use strict';
var browserify = require('browserify');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var log = require('gulplog');
var uglify = require('gulp-uglify');
var reactify = require('reactify');
gulp.task('javascript', function () {
// set up the browserify instance on a task basis
var b = browserify({
entries: './entry.js',
debug: true,
// defining transforms here will avoid crashing your stream
transform: [reactify]
});
return b.bundle()
.pipe(source('app.js', { sourcemaps: true }))
.pipe(buffer())
// Add transformation tasks to the pipeline here.
.pipe(uglify())
.on('error', log.error)
.pipe(gulp.dest('./dist/js/', { sourcemaps: '../sourcemaps/' }));
});
Gulp Version control: Browserify + Transforms
I attempted to create an example for you, but it's difficult to say what the most usable gulp script would be for your project. I'll add an example, but please don't consider it as a fix that's ready for general use. It attempts to mimic the behaviour your current gulp script has. You may want other behaviour in the long run for instance because the bundling this script creates may not be as optimized as other configurations or tooling would allow.
const { dest } = require("gulp"),
browserify = require("browserify"),
babelify = require("babelify"),
glob = require("glob"),
source = require("vinyl-source-stream"),
fileinclude = require("gulp-file-include"),
rename = require("gulp-rename"),
uglify = require("gulp-uglify-es").default,
notify = require("gulp-notify"),
browserSync = require("browser-sync").create(),
es = require("event-stream");
const changingScripts = (done) => {
// Define files you want to have as inputs
var files = ["src/js/main.js", ...glob.sync("src/js/pages/**/*.js")];
// Bundle each file separately so that file structure is preserved in
// dist
var tasks = files.map((file) => {
return (
browserify({
entries: [file],
debug: true,
transform: [
// Apply babel transforms here so that browserify knows how to bundle
// the files
babelify.configure({
presets: ["#babel/preset-env"],
}),
],
})
.bundle()
// Transform the stream content bable bundling returns into a gulp
// friendly format
.pipe(source(file))
// Not sure how fileinclude is used in your project. May be that it
// doesn't work when it's configured in this way.
.pipe(fileinclude())
.pipe(dest("dist/js"))
.pipe(
uglify({
toplevel: true,
}).on("error", notify.onError())
)
.pipe(
rename({
extname: ".min.js",
})
)
.pipe(dest("dist/js"))
.pipe(browserSync.stream())
);
});
return es.merge(tasks).on("end", done);
};
exports.default = changingScripts;
Gulp: Creating multiple bundles with Browserify

Systemjs-Builder - Cannot configure properly - Bundling Typescript into a package

I want to build a quick nodejs script to package a Typescript app as SystemJS modules, a lot like what Angular2 bundles look like.
I tried different configurations but I can't seem to put my finger on it, and haven't found clear enough documentation as of yet.
Note that for this "test", I am not using Gulp or Jspm at all, just systemjs-builder for the time being (and don't plan on using jspm at all either)
Here's what my "project" looks like:
---- Project's Root
-------- index.ts // export * from './modules/index' and eventually more
-------- modules
------------ index.ts // export * from './menu/index'
------------ menu
---------------- menu.component.ts // export class
---------------- menu.service.ts // export class
I want to package this under a single file, where I will have multiple SystemRegister modules that can be consumed in an app thereafter
I tried the following without success:
var Builder = require('systemjs-builder');
// optional constructor options
// sets the baseURL and loads the configuration file
var builder = new Builder('./modules');
builder.bundle('./modules/index.ts', {
/* SystemJS Configuration Here */
baseURL: './modules',
transpiler: 'typescript',
typescriptOptions: {
"module": "system",
"emitDecoratorMetadata": true,
"experimentalDecorators": true
},
defaultExtension: 'ts',
packages: {
'modules': {
defaultExtension: 'ts'
}
}
}, 'infrastructure.js')
.then(function() {
console.log('Build complete');
})
.catch(function(err) {
console.error(err);
})
First of all, the defaultExtension options doesn't seem to work at all
So when I do import {something} from 'filePath'; (without extension), it tries to load filePath, instead of filePath.ts;
Second, if I try adding the .ts extension in my imports (which I don't want to do), it complains that the code is invalid (unexpected token #, unexpected token menuItem and so forth)
Anyone have a good example or some explanations on how this is supposed to work?
Thank you
here you have an example: angular typescript skeleton
build task looks like this:
const path = require('path');
const Builder = require('jspm').Builder;
const builder = new Builder();
const packageJson = require(path.join(config.projectDir, 'package.json'));
return beginBuild()
.then(buildSFX)
.catch((err) => console.log('Build Failed', err));
function beginBuild() {
builder.reset();
return builder.loadConfig(path.join(config.projectDir, packageJson.jspm.configFile))
}
function buildSFX() {
const appName = packageJson.name;
const distFileName = `${appName}.min.js`;
const outFile = path.join(config.distDir, distFileName);
const moduleName = 'app';
const buildConfig = {
format: 'global',
minify: true,
sourceMaps: true
};
return builder.buildStatic(moduleName, outFile, buildConfig);
}
and jspm conf looks like this:
System.config({
defaultJSExtensions: true,
transpiler: "typescript",
typescriptOptions: {
"tsconfig": "src/tsconfig.json"
},
paths: {
"github:*": "vendor/jspm_packages/github/*",
"npm:*": "vendor/jspm_packages/npm/*",
"app": "src/index"
}
/// ...
}
Why do you want to bundle typescript? Bundling is a method used for optimizing the delivery of source code to the browser. The browser doesn't know typescript, it only knows javascript (unless you do on the fly transpiling).

Passing environment-dependent variables in webpack

I'm trying to convert an angular app from gulp to webpack. in gulp I use gulp-preprocess to replace some variables in the html page (e.g. database name) depending on the NODE_ENV. What is the best way of achieving a similar result with webpack?
There are two basic ways to achieve this.
DefinePlugin
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
Note that this will just replace the matches "as is". That's why the string is in the format it is. You could have a more complex structure, such as an object there but you get the idea.
EnvironmentPlugin
new webpack.EnvironmentPlugin(['NODE_ENV'])
EnvironmentPlugin uses DefinePlugin internally and maps the environment values to code through it. Terser syntax.
Alias
Alternatively you could consume configuration through an aliased module. From consumer side it would look like this:
var config = require('config');
Configuration itself could look like this:
resolve: {
alias: {
config: path.join(__dirname, 'config', process.env.NODE_ENV)
}
}
Let's say process.env.NODE_ENV is development. It would map into ./config/development.js then. The module it maps to can export configuration like this:
module.exports = {
testing: 'something',
...
};
Just another option, if you want to use only a cli interface, just use the define option of webpack. I add the following script in my package.json :
"build-production": "webpack -p --define process.env.NODE_ENV='\"production\"' --progress --colors"
So I just have to run npm run build-production.
I investigated a couple of options on how to set environment-specific variables and ended up with this:
I have 2 webpack configs currently:
webpack.production.config.js
new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify('production'),
'API_URL': JSON.stringify('http://localhost:8080/bands')
}
}),
webpack.config.js
new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify('development'),
'API_URL': JSON.stringify('http://10.10.10.10:8080/bands')
}
}),
In my code I get the value of API_URL in this (brief) way:
const apiUrl = process.env.API_URL;
EDIT 3rd of Nov, 2016
Webpack docs has an example: https://webpack.js.org/plugins/define-plugin/#usage
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("5fa3b9"),
BROWSER_SUPPORTS_HTML5: true,
TWO: "1+1",
"typeof window": JSON.stringify("object")
})
With ESLint you need to specifically allow undefined variables in code, if you have no-undef rule on. http://eslint.org/docs/rules/no-undef like this:
/*global TWO*/
console.log('Running App version ' + TWO);
EDIT 7th of Sep, 2017 (Create-React-App specific)
If you're not into configuring too much, check out Create-React-App: Create-React-App - Adding Custom Environment Variables. Under the hood CRA uses Webpack anyway.
You can pass environment variables without additional plugins using --env
Webpack 2-4
webpack --config webpack.config.js --env.foo=bar
Webpack 5+ (without.)
webpack --config webpack.config.js --env foo=bar
Then, use the variable in webpack.config.js:
module.exports = function(env) {
if (env.foo === 'bar') {
// do something
}
}
Further Reading: Webpack 2.0 doesn't support custom command line arguments?
#2254
You can directly use the EnvironmentPlugin available in webpack to have access to any environment variable during the transpilation.
You just have to declare the plugin in your webpack.config.js file:
var webpack = require('webpack');
module.exports = {
/* ... */
plugins: [
new webpack.EnvironmentPlugin(['NODE_ENV'])
]
};
Note that you must declare explicitly the name of the environment variables you want to use.
To add to the bunch of answers personally I prefer the following:
const webpack = require('webpack');
const prod = process.argv.indexOf('-p') !== -1;
module.exports = {
...
plugins: [
new webpack.DefinePlugin({
process: {
env: {
NODE_ENV: prod? `"production"`: '"development"'
}
}
}),
...
]
};
Using this there is no funky env variable or cross-platform problems (with env vars). All you do is run the normal webpack or webpack -p for dev or production respectively.
Reference: Github issue
Since my Edit on the above post by thevangelist wasn't approved, posting additional information.
If you want to pick value from package.json like a defined version number and access it through DefinePlugin inside Javascript.
{"version": "0.0.1"}
Then, Import package.json inside respective webpack.config, access the attribute using the import variable, then use the attribute in the DefinePlugin.
const PACKAGE = require('../package.json');
const _version = PACKAGE.version;//Picks the version number from package.json
For example certain configuration on webpack.config is using METADATA for DefinePlugin:
const METADATA = webpackMerge(commonConfig({env: ENV}).metadata, {
host: HOST,
port: PORT,
ENV: ENV,
HMR: HMR,
RELEASE_VERSION:_version//Version attribute retrieved from package.json
});
new DefinePlugin({
'ENV': JSON.stringify(METADATA.ENV),
'HMR': METADATA.HMR,
'process.env': {
'ENV': JSON.stringify(METADATA.ENV),
'NODE_ENV': JSON.stringify(METADATA.ENV),
'HMR': METADATA.HMR,
'VERSION': JSON.stringify(METADATA.RELEASE_VERSION)//Setting it for the Scripts usage.
}
}),
Access this inside any typescript file:
this.versionNumber = process.env.VERSION;
The smartest way would be like this:
// webpack.config.js
plugins: [
new webpack.DefinePlugin({
VERSION: JSON.stringify(require("./package.json").version)
})
]
Thanks to Ross Allen
Just another answer that is similar to #zer0chain's answer. However, with one distinction.
Setting webpack -p is sufficient.
It is the same as:
--define process.env.NODE_ENV="production"
And this is the same as
// webpack.config.js
const webpack = require('webpack');
module.exports = {
//...
plugins:[
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
};
So you may only need something like this in package.json Node file:
{
"name": "projectname",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"debug": "webpack -d",
"production": "webpack -p"
},
"author": "prosti",
"license": "ISC",
"dependencies": {
"webpack": "^2.2.1",
...
}
}
Just a few tips from the DefinePlugin:
The DefinePlugin allows you to create global constants which can be configured at compile time. This can be useful for allowing different behavior between development builds and release builds. For example, you might use a global constant to determine whether logging takes place; perhaps you perform logging in your development build but not in the release build. That's the sort of scenario the DefinePlugin facilitates.
That this is so you can check if you type webpack --help
Config options:
--config Path to the config file
[string] [default: webpack.config.js or webpackfile.js]
--env Enviroment passed to the config, when it is a function
Basic options:
--context The root directory for resolving entry point and stats
[string] [default: The current directory]
--entry The entry point [string]
--watch, -w Watch the filesystem for changes [boolean]
--debug Switch loaders to debug mode [boolean]
--devtool Enable devtool for better debugging experience (Example:
--devtool eval-cheap-module-source-map) [string]
-d shortcut for --debug --devtool eval-cheap-module-source-map
--output-pathinfo [boolean]
-p shortcut for --optimize-minimize --define
process.env.NODE_ENV="production"
[boolean]
--progress Print compilation progress in percentage [boolean]
I found the following solution to be easiest to setup environment variable for Webpack 2:
For example we have a webpack settings:
var webpack = require('webpack')
let webpackConfig = (env) => { // Passing envirmonment through
// function is important here
return {
entry: {
// entries
},
output: {
// outputs
},
plugins: [
// plugins
],
module: {
// modules
},
resolve: {
// resolves
}
}
};
module.exports = webpackConfig;
Add Environment Variable in Webpack:
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
]
Define Plugin Variable and add it to plugins:
new webpack.DefinePlugin({
'NODE_ENV': JSON.stringify(env.NODE_ENV || 'development')
}),
Now when running webpack command, pass env.NODE_ENV as argument:
webpack --env.NODE_ENV=development
// OR
webpack --env.NODE_ENV development
Now you can access NODE_ENV variable anywhere in your code.
I prefer using .env file for different environment.
Use webpack.dev.config to copy env.dev to .env into root folder
Use webpack.prod.config to copy env.prod to .env
and in code
use
require('dotenv').config();
const API = process.env.API ## which will store the value from .env file
My workaround for the webpack version "webpack": "^4.29.6" is very simple.
//package.json
{
...
"scripts": {
"build": "webpack --mode production",
"start": "webpack-dev-server --open --mode development"
},
}
you can pass --mode parameter with your webpack commnad then in webpack.config.js
// webpack.config.json
module.exports = (env,argv) => {
return {
...
externals: {
// global app config object
config: JSON.stringify({
apiUrl: (argv.mode==="production") ? '/api' : 'localhost:3002/api'
})
}
}
And I use baseurl in my code like this
// my api service
import config from 'config';
console.log(config.apiUrl) // like fetch(`${config.apiUrl}/users/user-login`)
To add to the bunch of answers:
Use ExtendedDefinePlugin instead of DefinePlugin
npm install extended-define-webpack-plugin --save-dev.
ExtendedDefinePlugin is much simpler to use and is documented :-)
link
Because DefinePlugin lacks good documentation, I want to help out, by saying that it actually works like #DEFINE in c#.
#if (DEBUG)
Console.WriteLine("Debugging is enabled.");
#endif
Thus, if you want to understand how DefinePlugin works, read the c# #define doucmentation. link
Since Webpack v4, simply setting mode in your Webpack config will set the NODE_ENV for you (via DefinePlugin). Docs here.
Here is a way that has worked for me and has allowed me keep my environment variables DRY by reusing a json file.
const webpack = require('webpack');
let config = require('./settings.json');
if (__PROD__) {
config = require('./settings-prod.json');
}
const envVars = {};
Object.keys(config).forEach((key) => {
envVars[key] = JSON.stringify(config[key]);
});
new webpack.DefinePlugin({
'process.env': envVars
}),
dotenv-webpack
A secure webpack plugin that supports dotenv and other environment variables and only exposes what you choose and use.
with some workaround with configuration based on defaults option to achieve that, once the package has .env.defaults file to as initial values for env variables you can use it for development and let .env for your production.
Usage
install the package
npm install dotenv-webpack --save-dev
Create a .env.defaults file
API_URL='dev_url/api/'
create a .env file leave it empty, let defaults works, update it on your deploy process
config webpack - webpack.config.js
new Dotenv({
defaults: true
})
dev environement test file.js
console.log(process.env.API_URL)
// Outputs: dev_url/api/
on build, update empty .env file
API_URL='prod_url/api/'
dotenv-webpack will use this to and override env.defaults
prod environement test file.js
console.log(process.env.API_URL)
// Outputs: prod_url/api/
dotenv-webpack
dotenv-defaults
I'm not a huge fan of...
new webpack.DefinePlugin({
'process.env': envVars
}),
...as it does not provides any type of security. instead, you end up boosting your secret stuff, unless you add a webpack to gitignore 🤷‍♀️ there is a better solution.
Basically with this config once you compile your code all the process env variables will be removed from the entire code, there is not going to be a single process.env.VAR up thanks to the babel plugin transform-inline-environment-variables
PS if you do not want to end up with a whole bunch of undefines, make sure you call the env.js before webpack calls babel-loader, that's why it is the first thing webpack calls. the array of vars in babel.config.js file must match the object on env.js. now there is only one mow thing to do.
add a .env file put all your env variables there, the file must be at the root of the project or feel free to add it where ever u want, just make sure to set the same location on the env.js file and also add it to gitignore
const dotFiles = ['.env'].filter(Boolean);
if (existsSync(dotFiles)) {
require("dotenv-expand")(require("dotenv").config((dotFiles)));
}
If you want to see the whole babel + webpack + ts get it from heaw
https://github.com/EnetoJara/Node-typescript-babel-webpack.git
and same logic applies to react and all the other 💩
config
---webpack.js
---env.js
src
---source code world
.env
bunch of dotFiles
env.js
"use strict";
/***
I took the main idea from CRA, but mine is more cooler xD
*/
const {realpathSync, existsSync} = require('fs');
const {resolve, isAbsolute, delimiter} = require('path');
const NODE_ENV = process.env.NODE_ENV || "development";
const appDirectory = realpathSync(process.cwd());
if (typeof NODE_ENV !== "string") {
throw new Error("falle and stuff");
}
const dotFiles = ['.env'].filter(Boolean);
if (existsSync(dotFiles)) {
require("dotenv-expand")(require("dotenv").config((dotFiles)));
}
process.env.NODE_PATH = (process.env.NODE_PATH || "")
.split(delimiter)
.filter(folder => folder && isAbsolute(folder))
.map(folder => resolve(appDirectory, folder))
.join(delimiter);
const ENETO_APP = /^ENETO_APP_/i;
module.exports = (function () {
const raw = Object.keys ( process.env )
.filter ( key => ENETO_APP.test ( key ) )
.reduce ( ( env, key ) => {
env[ key ] = process.env[ key ];
return env;
},
{
BABEL_ENV: process.env.ENETO_APP_BABEL_ENV,
ENETO_APP_DB_NAME: process.env.ENETO_APP_DB_NAME,
ENETO_APP_DB_PASSWORD: process.env.ENETO_APP_DB_PASSWORD,
ENETO_APP_DB_USER: process.env.ENETO_APP_DB_USER,
GENERATE_SOURCEMAP: process.env.ENETO_APP_GENERATE_SOURCEMAP,
NODE_ENV: process.env.ENETO_APP_NODE_ENV,
PORT: process.env.ENETO_APP_PORT,
PUBLIC_URL: "/"
} );
const stringyField = {
"process.env": Object.keys(raw).reduce((env, key)=> {
env[key]=JSON.stringify(raw[key]);
return env;
},{}),
};
return {
raw, stringyField
}
})();
webpack file with no plugins troll
"use strict";
require("core-js");
require("./env.js");
const path = require("path");
const nodeExternals = require("webpack-node-externals");
module.exports = env => {
return {
devtool: "source-map",
entry: path.join(__dirname, '../src/dev.ts'),
externals: [nodeExternals()],
module: {
rules: [
{
exclude: /node_modules/,
test: /\.ts$/,
use: [
{
loader: "babel-loader",
},
{
loader: "ts-loader"
}
],
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: "file-loader",
},
],
},
],
},
node: {
__dirname: false,
__filename: false,
},
optimization: {
splitChunks: {
automaticNameDelimiter: "_",
cacheGroups: {
vendor: {
chunks: "initial",
minChunks: 2,
name: "vendor",
test: /[\\/]node_modules[\\/]/,
},
},
},
},
output: {
chunkFilename: "main.chunk.js",
filename: "name-bundle.js",
libraryTarget: "commonjs2",
},
plugins: [],
resolve: {
extensions: ['.ts', '.js']
} ,
target: "node"
};
};
babel.config.js
module.exports = api => {
api.cache(() => process.env.NODE_ENV);
return {
plugins: [
["#babel/plugin-proposal-decorators", { legacy: true }],
["#babel/plugin-transform-classes", {loose: true}],
["#babel/plugin-external-helpers"],
["#babel/plugin-transform-runtime"],
["#babel/plugin-transform-modules-commonjs"],
["transform-member-expression-literals"],
["transform-property-literals"],
["#babel/plugin-transform-reserved-words"],
["#babel/plugin-transform-property-mutators"],
["#babel/plugin-transform-arrow-functions"],
["#babel/plugin-transform-block-scoped-functions"],
[
"#babel/plugin-transform-async-to-generator",
{
method: "coroutine",
module: "bluebird",
},
],
["#babel/plugin-proposal-async-generator-functions"],
["#babel/plugin-transform-block-scoping"],
["#babel/plugin-transform-computed-properties"],
["#babel/plugin-transform-destructuring"],
["#babel/plugin-transform-duplicate-keys"],
["#babel/plugin-transform-for-of"],
["#babel/plugin-transform-function-name"],
["#babel/plugin-transform-literals"],
["#babel/plugin-transform-object-super"],
["#babel/plugin-transform-shorthand-properties"],
["#babel/plugin-transform-spread"],
["#babel/plugin-transform-template-literals"],
["#babel/plugin-transform-exponentiation-operator"],
["#babel/plugin-proposal-object-rest-spread"],
["#babel/plugin-proposal-do-expressions"],
["#babel/plugin-proposal-export-default-from"],
["#babel/plugin-proposal-export-namespace-from"],
["#babel/plugin-proposal-logical-assignment-operators"],
["#babel/plugin-proposal-throw-expressions"],
[
"transform-inline-environment-variables",
{
include: [
"ENETO_APP_PORT",
"ENETO_APP_NODE_ENV",
"ENETO_APP_BABEL_ENV",
"ENETO_APP_DB_NAME",
"ENETO_APP_DB_USER",
"ENETO_APP_DB_PASSWORD",
],
},
],
],
presets: [["#babel/preset-env",{
targets: {
node: "current",
esmodules: true
},
useBuiltIns: 'entry',
corejs: 2,
modules: "cjs"
}],"#babel/preset-typescript"],
};
};
now 2020, i am face to same question, but for this old question, there are so many new answer, just list some of it:
this is webpack.config.js
plugins: [
new HtmlWebpackPlugin({
// 1. title is the parameter, you can use in ejs template
templateParameters:{
title: JSON.stringify(someting: 'something'),
},
}),
//2. BUILT_AT is a parameter too. can use it.
new webpack.DefinePlugin({
BUILT_AT: webpack.DefinePlugin.runtimeValue(Date.now,"some"),
}),
//3. for webpack5, you can use global variable: __webpack_hash__
//new webpack.ExtendedAPIPlugin()
],
//4. this is not variable, this is module, so use 'import tt' to use it.
externals: {
'ex_title': JSON.stringify({
tt: 'eitentitle',
})
},
the 4 ways only basic, there are even more ways that i believe. but i think maybe this 4ways is the most simple.

Keep original typescript source maps after using browserify

Background: I am compiling 2 dependent TypeScript files to js, which produces also source maps (one source map per file) using tsc 1.0
I'm using -m commonjs and then use browserify to generate a single bundle.js
However I noticed that I get the original source map references twice in the bundle, which doesn't seem to work.
Passing --debug doesn't seem to do the trick either.
I had a feeling this issue: https://github.com/substack/node-browserify/issues/325 is somewhat related, but I couldn't figure out how the issue was resolved.
Also https://github.com/substack/browser-pack was suggested, but again I don't fully understand how to use it, is it a replacement to browserify?
Bottom line, I would like to merge the 2 js files but "merge" the js to ts source maps using browserify. Is that possible?
tsify is a browserify plugin that is better and replaces e.g. typescriptifier.
npm install tsify browserify watchify
You use tsify like this:
browserify src/index.ts -p tsify --debug -o build/index.js
Notice that this supports browserify --debug switch, no extra tricks required. So you can also use it with watchify like this:
watchify src/index.ts -p tsify --debug -o build/index.js
Using the minifyify browserify plugin I believe you can use TypeScript with Browserify and retain the source maps. After compiling the TypeScript files you should be able to pass the "entry" file (the one that imports the other one via commonjs syntax) through browserify with the minifyify plugin.
var browserify = require('browserify'),
bundler = new browserify();
bundler.add('entry.js');
bundler.plugin('minifyify', {map: 'bundle.js.map'});
bundler.bundle({debug: true}, function (err, src, map) {
if (err) console.log(err);
fs.writeFileSync('bundle.js', src);
fs.writeFileSync('bundle.js.map', map);
});
Here is my working solution:
var settings = {
projectName : "test"
};
gulp.task("bundle", function() {
var mainTsFilePath = "src/main.ts";
var outputFolder = "bundle/src/";
var outputFileName = settings.projectName + ".min.js";
var pkg = require("./package.json");
var banner = [
"/**",
" * <%= pkg.name %> v.<%= pkg.version %> - <%= pkg.description %>",
" * Copyright (c) 2015 <%= pkg.author %>",
" * <%= pkg.license %>",
" */", ""
].join("\n");
var bundler = browserify({
debug: true,
standalone : settings.projectName
});
// TS compiler options are in tsconfig.json file
return bundler.add(mainTsFilePath)
.plugin(tsify)
.bundle()
.pipe(source(outputFileName))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(uglify())
.pipe(header(banner, { pkg : pkg } ))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(outputFolder));
});
I created example project.
You can run it with $(npm bin)/gulp build --env=dev for development environment and source maps will be generated.
There is gulpfile.js:
'use strict';
var path = require('path'),
gulp = require('gulp'),
del = require('del'),
typescript = require('gulp-typescript'),
sourcemaps = require('gulp-sourcemaps'),
browserify = require('browserify'),
source = require('vinyl-source-stream'),
buffer = require('vinyl-buffer'),
uglify = require('gulp-uglify'),
gutil = require('gulp-util'),
inject = require('gulp-inject'),
babel = require('gulp-babel'),
argv = require('yargs').argv;
var devEnvironment = 'dev',
prodEnvironment = 'prod',
environment = argv.env || prodEnvironment,
isDevelopment = environment === devEnvironment;
var projectPath = __dirname,
srcDir = 'src',
srcPath = path.join(projectPath, srcDir),
buildDir = path.join('build', environment),
buildPath = path.join(projectPath, buildDir),
distDir = 'dist',
distRelativePath = path.join(buildDir, distDir),
distPath = path.join(buildPath, distDir);
var tsSrcPath = path.join(srcPath, 'typescript'),
tsGlob = path.join(tsSrcPath, '**', '*.ts'),
tsBuildPath = path.join(buildPath, 'tsc');
var indexHtmlName = 'index.html',
indexJsName = 'index.js';
var distIndexJsPath = path.join(distPath, 'index.js'),
distIndexHtmlPath = path.join(distPath, indexHtmlName);
var tsProject = typescript.createProject('tsconfig.json');
console.log('Environment: ' + environment);
gulp.task('clean', function () {
return del([buildPath]);
});
gulp.task('tsc', ['clean'], function () {
var stream = gulp.src([tsGlob]);
if (isDevelopment) {
stream = stream
.pipe(sourcemaps.init());
}
stream = stream
.pipe(typescript(tsProject))
.pipe(babel({
presets: ['es2015']
}));
if (isDevelopment) {
stream = stream.pipe(sourcemaps.write({sourceRoot: tsSrcPath}));
}
return stream.pipe(gulp.dest(tsBuildPath));
});
gulp.task('bundle', ['tsc'], function () {
var b = browserify({
entries: path.join(tsBuildPath, indexJsName),
debug: isDevelopment
});
var stream = b.bundle()
.pipe(source(indexJsName))
.pipe(buffer());
if (!isDevelopment) {
stream = stream.pipe(uglify());
}
return stream
.on('error', gutil.log)
.pipe(gulp.dest(distPath));
});
gulp.task('build', ['bundle'], function() {
return gulp.src(path.join(srcPath, indexHtmlName))
.pipe(inject(gulp.src([distIndexJsPath], {read: false}), {ignorePath: distRelativePath, addRootSlash: true}))
.pipe(gulp.dest(distPath));
});
You should pay attention to lines:
stream = stream.pipe(sourcemaps.write('', {sourceRoot: tsSrcPath})); - write inline source maps with sourceRoot pointing to your typescript sources path. Inline maps are written directly to .js files generated by tsc to build/dev/tsc.
debug: isDevelopment - in development environment make browserify generate his own source maps for resulting bundle build/dev/dist/index.js file so it will have source maps referencing .js files from build/dev/tsc which in turn have source maps referencing .ts files from src/typescript.
With this setup you will be able to see and debug .ts files in browser:
I faced similar issue when trying to debug my Angular2 app running in Chrome in Visual Studio Code (Using Debugger for Chrome extension)
I use gulp as my task runner and my setup is as follows:
Typescript files -> tsc -> intermediate es5 js -> browserify (plus uglify in production build) -> compiled bundle
My directory structure is as follows:
|- src
|- my .ts files here
|- main.ts - my entry file
|- dist
|- intermediate files go here
|- web
|- app.js - final bundle
|- app.js.map - final bundle map
|- gulpfile.js
gulpfile.js:
var gulp = require('gulp'),
tsc = require('gulp-typescript'),
browserify = require('browserify'),
uglify = require('gulp-uglify'),
sourcemaps = require('gulp-sourcemaps'),
source = require('vinyl-source-stream'),
buffer = require('vinyl-buffer');
gulp.task('tsc', [], () => {
return gulp.src(['src/**/*.ts'])
.pipe(sourcemaps.init())
.pipe(tsc({
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [ "es2015", "dom" ],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}))
.pipe(sourcemaps.write(null, {
"sourceRoot": function(file) {
let parts = file.relative.split('\\');
let root = Array(parts.length + 1).join('../') + 'src';
return root;
}
}))
.pipe(gulp.dest('dist/'));
});
gulp.task('bundle', ['tsc'], () => {
let b = browserify({
entries: 'dist/main.js',
debug: true,
});
return b.bundle()
.pipe(source('app.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(sourcemaps.write('./', {
"sourceRoot": "../",
}))
.pipe(gulp.dest('web/'));
})
gulp.task('default', ['bundle']);
Explanation/reasoning:
For some reason browserify doesn't read and parse .js.map files linked in .js file (via special comment at the end) but it does when the source map is embedded in js file. So, by passing null instead of path to sourcemaps it will embed it at the end of generated .js file.
Next issue I noticed was that sourcemaps doesn't automatically follow directory structure (add '../' to sourceRoot when it goes to next directory level), so I made a quick function to complement this. Keep in mind that it only works on Windows - on Linux you'd have to change split character.
function(file) {
let parts = file.relative.split('\\'); // put '/' here on Linux
let root = Array(parts.length + 1).join('../') + 'src';
return root;
}
Certainly there is a way to detect correct path separator, I'm debugging only on Windows thus it's not important for my purposes.
I hope it helps someone, cause I've spent whole Sunday morning tracking down this problem.

Categories