对webpack的运用做一些解释

如何支持多出口?

在用webpack打包的时候,我们经常遇到一个问题,那就是由于path的设置,导致我们不能分目录打包我们想要的文件,这样的问题怎么办呢?
其实这样的问题很好解决。我们只需要在entry里做一些改变即可。

以我们的项目为例。我需要对资源生成一张资源地图,按照资源地图来制作入口配置。

var path = require('path');
function getSourceMap(list) {
    var entry = Object.create(null);
    list.forEach(function (src) {
        // 砍掉./static/前缀和.extname后缀
        // 我们的项目结构,读者可以自行配置,不用拘泥于伪代码
        entry[src.slice(9, src.lastIndexOf('.'))] = src;
    });
    return entry;
}

生成的资源入口最终变成了

entry = {
    'market/js/pages/appIndexPage': './static/market/js/pages/appIndexPage.js',
    'market/js/pages/appOrderPage': './static/market/js/pages/appOrderPage.js'
    ...
}

这时候,只要简单的将输入静态资源的出口规定好,我们的多出口输出配置就完成了

output: {
        path: path.resolve(__dirname, 'www/static-mobile/' + version),
        filename: "[name].js"
    }

最终输出的文件结构如下

+www
    +static-mobile
        +dev
            +market
               + js
                    +pages
                         appIndexPage.js
                         appOrderPage.js
                         ....

如何将webpackDevServer与thinkjs的服务结合起来

本地构建的过程中,我们的webpackDevServer其实只充当着静态资源服务器的角色。这个时候我们就不可避免的要访问thinkjs的服务来支撑每个页面的展示。


怎么办呢?如果启用nginx,对于前端来说,又是一坨没有接触过的东西,不如用我们熟悉的东西来做。

var d = webpack(options);

var WebpackDevServer = require('webpack-dev-server');

var compiler = webpack(options);
var server = new WebpackDevServer(compiler, {
    contentBase:path.join(__dirname, 'www/'),
    compress: true,
    hot: true,
    open: true,
    stats: { colors: true }
    ,
    proxy: [
        {
            path: '/static\-mobile',
            target: 'http://localhost:3000',
            pathRewrite: {
                '^/static-mobile': '/qietv-mobile'
            }
        },
        {
            path: '*',
            target: 'http://localhost:8361',
            context: function (pathname, req) {
                if (/^\/(static\-mobile|qietv\-mobile)/.test(pathname)) {
                    return false;
                }
                return true;
            },
        }
    ]
});

我们所处的项目的thinkjs服务端口是8361,其实只要将非静态文件夹全部转到thinkjs服务上就行。在网上有很多其他答案,会告诉你只用跟着他的答案写就行了,却没有告诉你为什么。要搞清楚proxy的详细配置方法,还是需要去看webpack-dev-server的文档和http-proxy-middleware的文档(因为devServer使用的是http-proxy-middleware)。

什么?我的热更新找不到输出文件?

同学们可能会疑惑,我启动了devServer.hot = true,入口也填入了webpack-dev-server/client,plugin也插入了,依然找不到文件,为什么?这个问题在于publicPath设置的错误(publicPath需要和output.publicPath一致)

var server = new WebpackDevServer(compiler, {
    contentBase:path.join(__dirname, 'www/'),
    compress: true,
    hot: true,
    inline: true,
    // publicPath不填默认是'/',
    // 在这里不填的话,我们原本输出到./www/qietv-mobile/dev/client/js/app.js
    // 访问的时候就变成了 'http://localhost:3000/client/js/app.js'才访问得到.
    // 加上一个访问path,就可以成功写出了。
    // publicPath: "/qietv-mobile/" + version + '/'
}

什么?我设置了publicPath以后依然无法热更新?

在设置完publicPath以后记得在pulgin里加入new webpack.HotModuleReplacementPlugin()。除此之外,你还需要在入口文件里允许需要的模块热更新才行。

// 允许所有子模块热更新
if (module.hot) {
    module.hot. accept();
    // 只允许某一个模块热更新
    // module.hot. accept('./a');
    // 允许多个指定模块热更新
    // module.hot. accept(['./a', './b']);
}

但是开发的时候,我们并不是很想写这个东西对吧,所以此时我们可以借助插件webpack-module-hot-accept(这个插件是一个loader,用于给经过的js增加一个module.hot.accept(xxx)的包装)。用法请自行百度该插件

webpack如何和别的观察系统结合

我们有这么一种需求,需要将jade文件观察(jade并不由我们的webpack引入),此时如果jade更新,我们需要刷新页面。作为一个懒人我们又懒得按下f5去刷新,这时候怎么办呢?

var webpack = require('webpack');
var config = require('./webpack.config.js');
var webpackDevServer = require('webpack-dev-server');
var server = webpackDevServer(webpack(config),  {
    // someoptions
});
var fs = require('fs-extra');
// 伪代码,
watch('/watchedDir', function (event) {
    if (event.filepath.test(/\.jade$/)) {
        fs.copy(event.filepath, targetDist)
           .then(function () {
                server.sockWrite(server.sockets, "content-changed");
          })
    }
})

webpack-dev-server向外暴露了sockWrite接口,用于指挥开发页面执行一些命令(webpack-dev-server使用sockjs和页面进行通信)。当发布content-changed事件的时候,页面接收到,会被直接刷新。因为content-changed事件对应的操作如下(入口应加入webpack-dev-server/client):

"content-changed": function() {
    log("info", "[WDS] Content base changed. Reloading...")
    self.location.reload();
},

哪些资源可以使用取巧的方式使本地开发更加便捷

如果我们的页面上有一些资源并不需要编译,仅仅只是简单的移动一下位置,那么我们简单做一个快捷方式过去就行了。。。这样可以省却本地开发复制移动这种无聊的事。。。

var views = glob.sync(path.resolve(p, '**/views'));

views.forEach(function (viewSrc) {
    fs.ensureSymlinkSync(viewSrc, path.resolve(__dirname, 'view', viewSrc.match(/\/([a-zA-Z0-9\-]+)\/views$/)[1]));
});

我跟你说这个只是个符号连接,小心你操作文件的时候把文件搞空了。

什么时候需要用到happypack

实际上,happypack是为了解决大量计算速度上不来的问题。我们做文件移动,复制,这些事情的时候基本用不上。那什么时候用呢?首先我们要明白happypack到底是什么东西。happypack本身管理了一些线程,充分利用cpu来讲计算密集的事情时间缩短(事实上他这个说法有误,他用的是chird_process,使用的是子进程,通信用的是IPC,当然应该是进程池。但是大家写这种东西习惯性的取这种名字,那就这样咯)。我们在做什么事情的时候会用到cpu计算呢?在解析es6文本,解析其他预编译语言的时候,就会用到cpu进行计算。所以在这种时候,我们需要上happypack进行构建。
happypack参与构建的姿势参考以下片段

// 例子是掘金上挖过来的,webpack1的写法,见谅
var babelQuery = webpackConfig.babel;

// 这个size可以使用cpu num + 1进行代替
var happyThreadPool = HappyPack.ThreadPool({ size: 25 });
function createHappyPlugin(id, loaders) {
  console.log('id', id)
  return new HappyPack({
    id: id,
    loaders: loaders,
    threadPool: happyThreadPool,

    // disable happy caching with HAPPY_CACHE=0
    cache: true,

    // make happy more verbose with HAPPY_VERBOSE=1
    verbose: process.env.HAPPY_VERBOSE === '1',
  });
}
webpackConfig.module = {};

webpackConfig.module = {
  loaders: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'happypack/loader?id=js',
    },
    {
      test: /\.jsx$/,
      loader: 'happypack/loader?id=jsx',
    },
    {
      test(filePath) {
        return /\.css$/.test(filePath) && !/\.module\.css$/.test(filePath);
      },
      loader: ExtractTextPlugin.extract('style', 'happypack/loader?id=cssWithoutModules')
    },
    {
      test: /\.module\.css$/,
      loader: ExtractTextPlugin.extract('style', 'happypack/loader?id=cssWithModules')
    },
    {
      test(filePath) {
        return /\.less$/.test(filePath) && !/\.module\.less$/.test(filePath);
      },
      loader: ExtractTextPlugin.extract('style', 'happypack/loader?id=lessWithoutModules')
    },
    {
      test: /\.module\.less$/,
      loader: ExtractTextPlugin.extract('style', 'happypack/loader?id=lessWithModules')
    }
  ],
}
if (!!handleFontAndImg) {
  webpackConfig.module.loaders.concat([
    { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'happypack/loader?id=woff' },
    { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'happypack/loader?id=woff2' },
    { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'happypack/loader?id=ttf' },
    { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'happypack/loader?id=eot' },
    { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'happypack/loader?id=svg' },
    { test: /\.(png|jpg|jpeg|gif)(\?v=\d+\.\d+\.\d+)?$/i, loader: 'happypack/loader?id=img' },
    { test: /\.json$/, loader: 'happypack/loader?id=json' },
    { test: /\.html?$/, loader: 'happypack/loader?id=html' }    
  ])
} else {
  webpackConfig.module.loaders.concat([
    { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&minetype=application/font-woff' },
    { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&minetype=application/font-woff' },
    { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&minetype=application/octet-stream' },
    { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file' },
    { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&minetype=image/svg+xml' },
    { test: /\.(png|jpg|jpeg|gif)(\?v=\d+\.\d+\.\d+)?$/i, loader: 'url?limit=10000' },
    { test: /\.json$/, loader: 'json' },
    { test: /\.html?$/, loader: 'file?name=[name].[ext]' }
  ])
}
webpackConfig.plugins.push(createHappyPlugin('js', ['babel?'+JSON.stringify(babelQuery)]))
webpackConfig.plugins.push(createHappyPlugin('jsx', ['babel?'+JSON.stringify(babelQuery)]))
webpackConfig.plugins.push(createHappyPlugin('cssWithoutModules', ['css?sourceMap&-restructuring!postcss']))
webpackConfig.plugins.push(createHappyPlugin('cssWithModules', ['css?sourceMap&-restructuring&modules&localIdentName=[local]___[hash:base64:5]!postcss']))
webpackConfig.plugins.push(createHappyPlugin('lessWithoutModules', ['css?sourceMap!postcss!less-loader?sourceMap']))
webpackConfig.plugins.push(createHappyPlugin('lessWithModules', ['css?sourceMap&modules&localIdentName=[local]___[hash:base64:5]!postcss!less-loader?sourceMap']))
if (!!handleFontAndImg) {
  webpackConfig.plugins.push(createHappyPlugin('woff', ['url?limit=10000&minetype=application/font-woff']))
  webpackConfig.plugins.push(createHappyPlugin('woff2', ['url?limit=10000&minetype=application/font-woff']))
  webpackConfig.plugins.push(createHappyPlugin('ttf', ['url?limit=10000&minetype=application/octet-stream']))
  webpackConfig.plugins.push(createHappyPlugin('eot', ['file']))
  webpackConfig.plugins.push(createHappyPlugin('svg', ['url?limit=10000&minetype=image/svg+xml']))
  webpackConfig.plugins.push(createHappyPlugin('img', ['url?limit=10000']))
  webpackConfig.plugins.push(createHappyPlugin('json', ['json']))
  webpackConfig.plugins.push(createHappyPlugin('html', ['file?name=[name].[ext]']))
}

本地开发需要全量构建吗?

本地开发追求的是开发速度,并不需要全量构建,甚至可以说,我们只需要指定子目录运行webpackdev即可。在启动本地开发的构建服务的时候,最初只会构建一次,用于访问其他项目模块使用。这样的代码是修改其他模块并不会被watch,但我们追求的是开发一个模块就专心一个模块的开发即可。这样才能十分明显的提高效率。

为什么我的css不更新!!

有这么一个经典的场景,a君问:

 module: {
        loaders: [
            {//抽离css
                test: /\.css$/,
                loader: ExtractTextPlugin.extract({ 
                fallback: 'style-loader', 
                use: 'css-loader', 
                publicPath: '/dist/' })
            }

我代码这样写用了ExtractTextPlugin为啥更新不了啊!


首先要说,webpack官方直接就告诉你了,我们在开发环境下,不推荐你用ExtractTextPlugin。因为1是开发环境不需要考虑加载速度和连接数量,2是ExtractTextPlugin这个插件也是将option合并以后返回给webpack进行处理,本身输出的非js模块,不支持热替换。这种情况下直接使用其他loader打包成js明显才是正道(支持热更新了)。在进行全量构建或者输出构建的时候再将css添加进去会比较明智。


geeeger
459 声望35 粉丝

我是一个智障,你害怕了吗?我的破文章如果对您有用的话,请赏一个,感谢