webpack-dev-ser原理解析

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

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已启动...')
});

其余的基本配置不太复杂,不再一一介绍

多页面应用渲染实例:多页面集成单页面应用实例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值