webpack-dev-server 运行原理

webpck-dev-ser作为开发时的服务非常方便,本文将对webpack-dev-server从原理层次解析,来看看它是如何实现服务和热更新的。

指令说明:

  -- webpack-dev-server 实现自动打包编译功能(每次修改JS文件后,都需要webpack执行打包重新生成JS文件。
         1、它会把整个项目以localhost服务形式运行起来,虚拟了一个服务器;
         2、webpack-dev-server会把webpack打包输出文件会被托管于(URL)根路径(本地磁盘dist目录下的不会发生改变),可以直接服务器根路径+输出JS文件名访问到)
  -- open,编译完自动打开浏览器
  -- port 端口,更改运行端口(默认8080-- contentBase 路径,更改内容根路径(默认服务器根路径、项目根路径),也是托管路径,可以设置为src即刚打开浏览器就访问到页面。引用路径时需要注意这个(例如项目根路径有node-modules文件夹,默认可以访问到;修改为src,即根路径变为src,手动引用时会访问不到)安装了html-webpack-plugin后,页面也托管于根路径可以直接访问到,此参数可不需要。
  -- hot,热重载、热跟新,页面异步刷新,减少不必要的刷新请求;打补丁,而不是重新编译,减少不必要的代码跟新。

1,webpack-dev-server的服务原理

基于express,搭建了一个http服务,根据路由返回不同的内容

2,静态资源服务

webpack-dev-server使用了webpack-dev-middleware,改变了webpack打包的输出地址,使用了memory-fs模块将打包资源输出到内存中;

基于内存中的文件,根据路径,express使用中间件static搭建了静态文件服务器;

监控:用chokidar来监视文件变化, server的内部维护的有一个socket集合

3,单页面应用路由对应

使用了包connect-history-api-fallback,该包也是一个express中间件,用来对资源重定向

对于配置:

historyApiFallback可以配置为true和对象

 
var server = new WebpackDevServer(compiler, {
  //contentBase:path.resolve(__dirname,"/build"),
  publicPath: config.output.publicPath,
  // hot: true,
  headers: {
    "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
  },
  historyApiFallback: true,
  stats: {
      colors: true // 用颜色标识
  },
  // progress: true,
  port: 9000,
  overlay: {
    warnings: true,
    errors: true
  },
  noInfo: false
});
 
server.listen(9000, 'localhost', (err, res) => {
if (err) {
  return console.log(err)
}
console.log('webpack 9000已启动...')
});

4,contentBase,contentBasePublicPath, transportMode等默认参数的设置

在utils/normalizeOptions.js文件中设置:

源码:

function normalizeOptions(compiler, options) {
  // Setup default value
  options.contentBase =
    options.contentBase !== undefined ? options.contentBase : process.cwd();
 
  // Setup default value
  options.contentBasePublicPath = options.contentBasePublicPath || '/';
 
  // normalize transportMode option
  if (options.transportMode === undefined) {
    options.transportMode = {
      server: 'sockjs',
      client: 'sockjs',
    };
  } else {
    switch (typeof options.transportMode) {
      case 'string':
        options.transportMode = {
          server: options.transportMode,
          client: options.transportMode,
        };
        break;
      // if not a string, it is an object
      default:
        options.transportMode.server = options.transportMode.server || 'sockjs';
        options.transportMode.client = options.transportMode.client || 'sockjs';
    }
  }
 
  if (!options.watchOptions) {
    options.watchOptions = {};
  }
}

上面代码设置了webpack-dev-server一些重要参数,了解默认参数,有助于了解options选项的含义

5,比较重要的options.publicPath

publicPath在源码中有两个地方用到

分别是:routes.js,createConfig

部分源码:

if (!options.publicPath) {
    // eslint-disable-next-line
    options.publicPath =
      (firstWpOpt.output && firstWpOpt.output.publicPath) || '';
 
    if (
      !isAbsoluteUrl(String(options.publicPath)) &&
      options.publicPath[0] !== '/'
    ) {
      options.publicPath = `/${options.publicPath}`;
    }
  }
app.get('/webpack-dev-server', (req, res) => {
    res.setHeader('Content-Type', 'text/html');
 
    res.write(
      '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>'
    );
 
    const outputPath = middleware.getFilenameFromUrl(options.publicPath || '/');
    const filesystem = middleware.fileSystem;
 
    writeDirectory(options.publicPath || '/', outputPath);
 
    res.end('</body></html>');
 
    function writeDirectory(baseUrl, basePath) {
      const content = filesystem.readdirSync(basePath);
 
      res.write('<ul>');
 
      content.forEach((item) => {
        const p = `${basePath}/${item}`;
 
        if (filesystem.statSync(p).isFile()) {
          res.write(`<li><a href="${baseUrl + item}">${item}</a></li>`);
 
          if (/\.js$/.test(item)) {
            const html = item.substr(0, item.length - 3);
            const containerHref = baseUrl + html;
 
            const magicHtmlHref =
              baseUrl.replace(
                // eslint-disable-next-line
                /(^(https?:\/\/[^\/]+)?\/)/,
                '$1webpack-dev-server/'
              ) + html;
 
            res.write(
              `<li><a href="${containerHref}">${html}</a>` +
                ` (magic html for ${item}) (<a href="${magicHtmlHref}">webpack-dev-server</a>)` +
                `</li>`
            );
          }
        } else {
          res.write(`<li>${item}<br>`);
 
          writeDirectory(`${baseUrl + item}/`, p);
 
          res.write('</li>');
        }
      });
 
      res.write('</ul>');
    }
  });

publicPath指定了静态资源, html文件的路径

6,webpack配置中的output.publicPath和webpack-dev-server中的publicPath的区别

output.publicPath是指插入到html文件中静态资源路径的统一前缀

实例:

output: {
    path: path.resolve(__dirname, '../build'),
    publicPath: 'http://localhost:9000/a',
    filename: 'js/[name].js',
    chunkFilename: 'js/[name].chunk.js', 
    hotUpdateChunkFilename: '[id].[hash].hot-update.js',
    hotUpdateMainFilename: '[hash].hot-update.json'
  },

这时候在浏览器中:
在这里插入图片描述
webpack-dev-server中的publicPath规定了静态资源的路径,比如:

new WebpackDevServer(compiler, {
  //contentBase:path.resolve(__dirname,"/build"),
  publicPath:'http://localhost:9000/abc/',
  // hot: true,
  headers: {
    "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
  }
}

这时候的静态资源路径统一前缀是/abc,比如要访问a.js文件,就需要访问:

http://localhost:9000/abc/js/a.js

所以上面两种publicPath的配置是不合理的,会出现html中的资源访问不到静态资源,所以两种publicPath应该设置一致,比如可以设置:

new WebpackDevServer(compiler, {
  //contentBase:path.resolve(__dirname,"/build"),
  publicPath: config.output.publicPath,
  // hot: true,
}

7,多文件配置实例,主要原理是资源重定向

var server = new WebpackDevServer(compiler, {
  //contentBase:path.resolve(__dirname,"/build"),
  publicPath: config.output.publicPath,
  // hot: true,
  headers: {
    "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
  },
  historyApiFallback: {
    rewrites:[
      {from:/^\/a/,to:'/a.html'}, //以根‘/a’开始,以根‘/’结尾的请求,重定向到‘a.html’
      {from:/^\/b/,to:'/b.html'}, //以‘/b’开始的请求,重定向到‘b.html’
      {from:/./,to:'/views/404.html'} //不匹配上面的任意除了换行符之外的请求,重定向到‘404.html’
    ]
  },
  stats: {
      colors: true // 用颜色标识
  },
  // progress: true,
  port: 9000,
  overlay: {
    warnings: true,
    errors: true
  },
  noInfo: false
});
 
server.listen(9000, 'localhost', (err, res) => {
if (err) {
  return console.log(err)
}
console.log('webpack 9000已启动...')
});
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值