Node Server Zero Foundation-Automatic Overloading of Development Environment Files

Posted by Daguse on Tue, 02 Jul 2019 00:04:00 +0200

Include for use, modification and reprint have been obtained Tencent Cloud To grant authorization

Preface

In web front-end development, we will use the Watch module of Grunt, Gulp and Webpack tools to monitor file changes. What should the server do? In fact, file changes can still be monitored with the help of build tools, but we also need to automatically restart the service or hot overload. This article will introduce three common methods.

Plan 1: fs.watch

Using node's native fs.watch method to monitor file changes, the so-called "hot overload" is just to clear the file cache in memory in time. Examples are as follows:

const fs = require('fs'),
    path = require('path'),
    projectRootPath = path.resolve(__dirname, './src');

const watch = project => {
    require('./src'); // Start APP and automatically retrieve src/index.js
    try { // Listen Folder
        fs.watch(project, { recursive: true }, cacheClean)
    } catch(e) {
        console.error('watch file error');
    }
}
// Clear Cache
const cacheClean = () => {
    Object.keys(require.cache).forEach(function (id) {
        if (/[\/\\](src)[\/\\]/.test(id)) {
            delete require.cache[id]
        }
    })
}
// Start development mode
watch(projectRootPath);

Note: When referring to middleware in the server entry file src/index.js, a layer of functions is needed and modules are introduced in the way of require to clear the cache. For example:

// Introducing middleware or controllers
app.use(async (ctx, next) => {
    await require('./controllers/main.js')(ctx);
});
// Or introduce routing
app.use(async (ctx, next) => {
    await require('./router').routes()(ctx, next)
})

Solution 2: Application of Process Manager

Take PM2 as an example, supervisor, forever and other similar process management tools have the same characteristics, which are not discussed here.

PM2 is a Node application process manager with load balancing function. It has the watch configuration item, which is used to monitor the changes of the application directory and restart immediately when the changes occur. See: Auto restart apps on file change . He is a real restart, not a hot replacement.

Disadvantage: PM2 does not provide an elegant way to tell users when to restart or kill processes.
The following is a simple PM2 configuration (development environment) start.js to start the process node start.js.

const pm2 = require('pm2');

pm2.connect(function(err) {
    if (err) {
        console.error(err);
        process.exit(2);
    }

    pm2.start({
        "watch": ["./app"], // Open watch mode and listen for changes under app folder
        "ignore_watch": ["node_modules", "assets"], // Documents that ignore listening
        "watch_options": {
            "followSymlinks": false // Symbolic links are not allowed
        },
        name: 'httpServer',
        script: './server/index.js', // APP Entry
        exec_mode: 'fockMode', // FokModel is recommended in development mode
        instances: 1, // Only one CPU is enabled
        max_memory_restart: '100M' // Restart APP when 100M of memory is occupied
    }, function(err, apps) {
        pm2.disconnect(); // Disconnects from PM2
        if (err) throw err
    });
});

Every time the file is modified and saved (Ctrl+S), a black box flashes, indicating that the application has been restarted successfully.

Scheme 3: chokidar + babel

Chokidar is a layer of encapsulation for fs.watch/fs.watchFile/fsevents. Its advantages include resolution (from chokidar documents):

1. File names cannot be obtained under OS X.

2. Sublime can't get modification events after modifying files under OS X.

3. Modifying the file will trigger two events.

4. Document recursive monitoring is not provided.

5. High CPU utilization rate;

6,...

The reason for using babel here is to support the latest js syntax, including ES2017, Stage-x, and module syntax such as import/export default.

The following provides a complete listen overload configuration file, and explains its functions and significance through comments.

const projectRootPath = path.resolve(__dirname, '..'),
    srcPath = path.join(projectRootPath, 'src'),    // source file
    appPath = path.join(projectRootPath, 'app'),    // Compiled Output Folder
    devDebug = debug('dev'),
    watcher = chokidar.watch(path.join(__dirname, '../src'))

// Start chokidar to listen for file changes
watcher.on('ready', function () {
    // babel Compiler Folder Directory
    babelCliDir({
        outDir: 'app/',
        retainLines: true,
        sourceMaps: true
    }, [ 'src/' ]) // compile all when start

    require('../app') // Start APP (compiled file)

    // Adding listening methods
    watcher
        // Documentation Added
        .on('add', function (absPath) {
            compileFile('src/', 'app/', path.relative(srcPath, absPath), cacheClean)
        })
        // File modification
        .on('change', function (absPath) {
            compileFile('src/', 'app/', path.relative(srcPath, absPath), cacheClean)
        })
        // File deletion
        .on('unlink', function (absPath) {
            var rmfileRelative = path.relative(srcPath, absPath)
            var rmfile = path.join(appPath, rmfileRelative)
            try {
                fs.unlinkSync(rmfile)
                fs.unlinkSync(rmfile + '.map')
            } catch (e) {
                devDebug('fail to unlink', rmfile)
                return
            }
            console.log('Deleted', rmfileRelative)
            cacheClean(); //Clear Cache
        })
})

// Dynamic compilation of files
function compileFile (srcDir, outDir, filename, cb) {
    const outFile = path.join(outDir, filename),
        srcFile = path.join(srcDir, filename);

    try {
        babelCliFile({
            outFile: outFile,
            retainLines: true,
            highlightCode: true,
            comments: true,
            babelrc: true,
            sourceMaps: true
        }, [ srcFile ], {
            highlightCode: true,
            comments: true,
            babelrc: true,
            ignore: [],
            sourceMaps: true
        })
    } catch (e) {
        console.error('Error while compiling file %s', filename, e)
        return
    }
    console.log(srcFile + ' -> ' + outFile)
    cb && cb() // Usually to clear the cache
}
// Clear Cache
function cacheClean () {
    Object.keys(require.cache).forEach(function (id) {
        if (/[\/\\](app)[\/\\]/.test(id)) {
            delete require.cache[id]
        }
    })
    console.log('App Cache Cleaned...')
}
// Withdrawal of listening procedure
process.on('exit', function (e) {
    console.log('App Quit')
})

Note: In order to make the cache invalid, we also need to wrap a layer of functions in use and introduce routing in a require way.

app.use(async (ctx, next) => {
    await require('./router').routes()(ctx, next)
})

Solution 4: Developing plug-ins

Noemon and node-dev are plug-ins for node.js development edition, providing a simple and easy-to-use development environment. In the case of nodemon, global or local installation is possible
npm install nodemon -g
Then the development process is started by nodemon. / server. JS localhost 8080. Independent, simple, easy to use!

See: remy/nodemon

Sum up

Each method has different applicable scenarios. If you want to try the latest grammar, recommend scheme 3; if you want to be simple and fast, scheme 2 is a good choice.

Is that over?

What if I want to use the latest grammatical features and need to be as simple as PM2? babel build tools (such as webpack) are no stranger to each front-end development, and a PM2 is enough to solve all the problems.

Links to the original text: https://www.qcloud.com/community/article/476280

Topics: Javascript Webpack OS X supervisor sublime