Search code examples
cordovanpm

How do I load node_modules in Cordova? (web browser modules, not node.js specific)


I have created a new Cordova app using

cordova create MyApp

I wanted to use a couple of web libraries (no dependency on node.js) and so I installed them with npm. E.g.

npm install onsenui vue-onsenui --save-dev

The directory structure looks like:

config.xml
hooks/
node_modules/
package.json
platforms/
plugins/
res/
www/

The index.html file in www has script tags to include the libraries

<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="vue.js"></script>
<script type="text/javascript" src="onsenui.js"></script>
<script type="text/javascript" src="vue-onsenui.js"></script>

When running the cordova run browser command, the web server runs fine and displays the page as it should, including loading the cordova.js file, but it returns 404 for the other libraries.

Is there a way to use these node modules in Cordova without copying them into the www directory?


Solution

  • TLDR and simple

    Use the npm package: cordova-import-npm

    Long question and DIY

    You can use a hook before cordova prepare, that is, when the files are assembled for compiling.

    1. Edit your config.xml and add this line (not inside any platform but in the root, i.e. inside <widget):
    <hook src="hooks/importNpmPackages.js" type="before_prepare"/>
    
    1. Create the file hooks/importNpmPackages.js with this content
    const fse = require('fs-extra')
    const path = require('path')
    const twoSpaces = '  ' // for log indentation
    var projectRoot
    
    module.exports = function (context) {
      console.log(`${context.hook} : ${path.relative(context.opts.projectRoot, context.scriptLocation)}`)
    
      projectRoot = context.opts.projectRoot
      console.log(twoSpaces + 'Project root directory: ' + projectRoot)
    
      copyFile('jquery', path.join('dist', 'jquery.min.js'), path.join('js', 'res', 'jquery.min.js'))
    
      copyFile('bootstrap', path.join('dist', 'js', 'bootstrap.min.js'), path.join('js', 'res', 'bootstrap.min.js'))
      copyFile('bootstrap', path.join('dist', 'css', 'bootstrap.min.css'), path.join('css', 'res', 'bootstrap.min.css'))
    }
    
    function copyFile (npmPackage, // oficial name of the npm package from which the file is to be copied from
      fileRelativePath, // file path with respect to the main directory of the npm package (node_modules/<package>/)
      destFilePath) { // file's path to where it is copied, relative to the project www/ directory
      
      const packageDirFullpath = path.dirname(require.resolve(path.join(npmPackage, 'package.json')))
      const fileOriginFullPath = path.join(packageDirFullpath, fileRelativePath)
      const fileDestFullPath = path.join(projectRoot, 'www', destFilePath)
    
      fse.copySync(fileOriginFullPath, fileDestFullPath)
    
      const consoleMsg = npmPackage + ': ' +
        path.relative(projectRoot, fileOriginFullPath) + ' -> ' +
        path.relative(projectRoot, fileDestFullPath)
      console.log(twoSpaces + consoleMsg)
    }
    

    As you can see in this file I am copying jquery and bootsrap files using a function copyFile with this syntax:

    copyFile(
      '<npmPackageName>',
      '<path/of/originFile/relative/to/packageDir>',
      '<path/to/destFile/relative/to/wwwDir>'
    )
    

    I use this in all my cordova projects and it works like a charm. That's how I see it

    enter image description here

    The hook is also triggered upon cordova build because cordova build equals to cordova prepare && cordova compile.