Raw js imported as a real module

Modern javascript libraries are all made of modules. They can be plugged inside any project : web, server, batch or program.

That would be really straightforward to include any dependency as a single line of configuration inside a package.json file.

Unfortunately, some teams want to expose web javascript code in a raw format : no ‘export’, no ‘module.exports’, and even nothing injected inside ‘global’ or ‘window’. ES5-like format.

Naive var(s) and function(s) are declared. Private companies do so, and they minify/obfuscate their code.
Some teams even make a javascript code generator when the source code simply cannot be shared with other companies.

//this is a raw javascript file
var foo = "some important data"; 

function bar(){ 
  return "successful."; 
}; 
//This script is unmodifiable
//You are expected to use these symbols, without any access to them.

A simple option would suggest you to copy/paste the library inside your app.
Too bad it affects a lot the quality of your delivery : big chunks, troubles when the library is upgraded but not in your code, ‘package.json’ not explicit (some libs are not visible in the manifest although they are effectively included)

I have found another solution that I wish to share.
1°) Bundle that library in package.json (you can set the URL inside the version).
2°) Then import that lib in your code, but not with the ‘import’ or ‘require’ shortcut… write that boilerplate code :

//it is like an es6 import, excepted the fact that it also collects non exported variables and functions
const _imports = moduleImports => Object.assign({}, ...moduleImports.map(moduleImport => {
    //build a closure outside the non-structured code
    const theModule = new (eval('(function(){' + fs.readFileSync(`${moduleImport}.js`) + ';this.eval = (name) => eval(name)})'))()

    //pick the functions and var names
    const stringified = theModule.constructor.toString()
    const functions = (stringified.match(/function\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(/g)||[]).map(oneFunction => oneFunction.replace(/^function/g, '').replace('(', '').trim())
    const variables = (stringified.match(/(?:var|let|const)\s+([a-zA-Z_][.a-zA-Z_0-9]*)\s*=/g)||[]).map(oneFunction => oneFunction.replace(/^(var|let|const)\s/g, '').replace('=', '').trim())

    //resolve the references inside the module
    return Object.assign({}, ...functions.concat(variables).map(symbol => {try{return {[symbol]:theModule.eval(symbol)}}catch(e){}}).filter(symbol => symbol))
}))

3°) Call the _imports function where needed

// top of your js file
const { foo, bar } = _imports(['my-es5-module-1', 'my-es5-module-2'])
// your other imports
import { Component } from 'react'
// and it works...
console.log(foo)
console.log(bar())

There you go, use the invisible members of the lib like if they were really exported.
In this example, it also works when the lib is a set of javascript files that are dependent on each other but without import.

By the way,
Maybe you noticed that the layout of the blog has changed once again.
I am pretty sure that my visitors will prefer some rich content rather then a beautiful decoration, given the fact that the blog no longer talks about artistic concerns.
That is why I chose the most sober design I could find.

Webpack2 : an example, a real one ?

« Has this ever happened to you ? »
-> You work in a non open minded company, or a company willing to make some progress, but behind in tech.
-> The front part of your stack is in (Java/PHP/Python/You name it) + (naive javascript or some former trendy framework like jquery / backbone / handlebars), and your build tool is as shifted from reality as maven or ant can be.
-> You think you deserve more than to maintain such a legacy stack and to write IE9 compliant code

My solution was to think with Webpack2.

Rather than explaining you what it is, let me define you what it is not :

– Webpack is not (really) a build tool. It would rather be « a module emulation builder ». In other words, it uses all of your resources to bundle them into very few « big files ».
Which means that you will no longer see this :
[Long resources list loading]
…but actually two resources : a webpage and a big script. Which will save the patience of some mobile users, and avoid repetitive server requests to load the same code over and over again.

– Webpack is not a code minifier. It groups css, medias (sound/picture), javascript resources at once. Don’t call that a minifier.

– Webpack is not a compiler. It uses a grammar to filter which resources must be handled, and some rules to declare how they should be transpiled/compressed/grouped/moved/compiled. Actually, it uses some loaders that are able to do the compiling job. Like babel, to be able to code using the latest javascript version (ES2017) while targeting IE9… Like handlebars, even if it is old, if you like it.

– Webpack is not a file manipulation tool. But it can add/change/delete some text inside files, it can copy/cut/paste/zip/unzip some resources. Some plugins are dedicated to such operations, like the CopyWebpackPlugin, or the ExtractTextPlugin.

Enough talk, let’s take an example stack : a « mavened » java webapp with the following js stack :
handlebars, backbone, underscore, jquery, and an invasive homegrown framework to avoid some non homogeneous display.
1°) Start by removing all of the DIY maven plugins. Write this instead in the plugins section:

<plugin>
<groupid>com.github.eirslett</groupid>
                <artifactid>frontend-maven-plugin</artifactid>
                <version>1.3</version>
                <executions>
                    <execution>
                        <id>install node and yarn</id>
                        <goals>
                            <goal>install-node-and-yarn</goal>
                        </goals>
                        <phase>generate-resources</phase>
                        <configuration>
                            <yarnversion>v0.23.2</yarnversion>
                        </configuration>
                    </execution>
                    <execution>
                        <id>yarn install</id>
                        <goals>
                            <goal>yarn</goal>
                        </goals>
                        <phase>generate-resources</phase>
                        <configuration>
                            <arguments>install</arguments>
                        </configuration>
                    </execution>
                    <execution>
                        <id>webpack</id>
                        <goals>
                            <goal>webpack</goal>
                        </goals>
                        <phase>process-resources</phase>
                        <configuration>
                            <environmentvariables>
                                <node_env>${env}</node_env>
                                <version>${project.version}</version>
                                <m2_repository>${maven.repo.local}</m2_repository>
                                <uitk_version>${uitkCoreVersion}</uitk_version>
                            </environmentvariables>
                            <arguments>--hide-modules</arguments>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <nodeversion>v7.9.0</nodeversion>
                </configuration>
            </plugin>

2°) Start a small package.json, it will be the bootstrap of your webpack pipeline :

{
  "name": "my-webapp",
  "description": "EcmaScript pipeline to build the assets of my-webapp",
  "version": "1.0.0",
  "private": true,
  "devDependencies": {
    "webpack": "latest",
    "copy-webpack-plugin": "latest",
    "extract-text-webpack-plugin": "latest",
    "before-build-webpack": "latest",
    "noop-webpack-plugin": "latest",
    "modernizr-webpack-plugin": "latest",
    "style-loader": "latest",
    "css-loader": "latest",
    "less-loader": "latest",
    "img-loader": "latest",
    "url-loader": "latest",
    "imports-loader": "latest",
    "exports-loader": "latest",
    "babel-cli": "latest",
    "babel-core": "latest",
    "babel-loader": "latest",
    "babel-preset-env": "latest",
    "babel-preset-es2015": "latest",
    "babel-polyfill": "latest",
    "handlebars": "latest",
    "handlebars-loader": "latest",
    "file-loader": "latest",
    "modernizr": "latest",
    "modernizr-loader": "latest",
    "jquery": "latest",
    "unzip2": "latest",
    "underscore": "latest",
    "phoneparser": "latest",
    "intl-tel-input": "latest",
    "backbone": "latest",
    "moment": "latest",
    "i18n-js": "latest",
    "mkdirp": "latest",
    "ncp": "latest",
    "less": "latest",
    "glob": "latest",
    "properties-reader": "latest"
  },
  "engines": {
    "node": ">=7.9.0"
  }
}

3°) Write a small webpack pipeline (wepack.config.js) and test it immediately :

const webpack = require('webpack')
const path = require('path')
const fs = require('fs')
const unzip = require('unzip2')
const glob = require('glob')
const WebpackBeforeBuildPlugin = require('before-build-webpack')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const ModernizrWebpackPlugin = require('modernizr-webpack-plugin'

const javascriptFiles = glob.sync(`js/**/*.js`)
const target = path.join(__dirname, 'target/')

module.exports = {
    name: 'webapp-bundle',
    target: 'web',
    entry:  [{`js/bundles/bundle.js`: javascriptFiles,}], output: { path: target, filename: '[name].js' }, module: { rules: [ { test: /\.js$/, use: [{ loader: 'babel-loader', options: { presets: [['es2015']] } }] }, ] } }

That works, but there are so much work to do for a real webapp if you are not working on a hello world page or a calculator, and over all if you must cope with an homegrown framework from your company

4°) Add some plugins to your pipeline :
– prepare your assets before the build (unzip, copy, …) :WebpackBeforeBuildPlugin
– provide some variables that nobody wants to declare explicitely : jquery ($), underscore or lodash (_) :webpack.ProvidePlugin
– extract your assets in non js files, for example for css files : ExtractTextPlugin
– manipulate the results after the compilation step : CopyWebpackPlugin
my own plugins list looks like this one :

plugins: [beforeBuild, listAllHandlebarsFiles, provideGlobalVariables, new ModernizrWebpackPlugin(), /* compile, */ extractCss, copyResults, minify]

5°) Add some rules to help webpack understands what is supposed to happen for each case :

{
                test: /\.hbs$/,
                use: [{
                    loader: 'handlebars-loader',
                    options: {
                        knownHelpers: ['i18nMsg', 'block', 'include', 'includeJsBundle', 'eq', 'json', 'i18nJs',
                            'partial'],
                        partialDirs: [
                            path.join(__dirname, `${webpackAssetsFolder}views`)
                        ],
                        rootRelatve: `${webpackAssetsFolder}views`
                    }
                }],
            },
            {
                test: /\.js$/,
                use: [{
                    loader: 'babel-loader',
                    options: {
                        presets: [['es2015']]
                    }
                }]
            },
            {
                test: /\.less$/,
                use: extractCss.extract({
                    use: ['css-loader', 'less-loader']
                })
            },
            {
                test: /\.css$/,
                use: extractCss.extract({
                    fallback: 'style-loader',
                    use: 'css-loader'
                })
            },

5°) Adapt your homegrown framework to your pipeline. Unzip the resources of the framework and mix them with yours.

You can have a look at how I did at my work : https://gist.github.com/libetl/79023dec57e4f4a02b48ee05b8d34d68
The pipeline (I mean the webpack.config.js file) can be composed of all these steps (mainly) :

import some modules
definition of resources and paths
definitions of complex plugins
output folder and output bundles
source maps (yes or no ?)
webpack rules (which file for which operation)
plugins list

Please do this at home, or at school. You will make your app more reliable and efficient. Good luck.