weixin_39902545
weixin_39902545
2021-01-04 12:40

基于webpack搭建前端工程解决方案探索

本篇主要介绍webpack的基本原理以及基于webpack搭建前端项目工程化解决方案的思路。

下篇(还没写)探讨下对于Node.js作为后端的项目工程化、模块化、前后端共享代码、自动化部署的做法。

关于前端工程

下面是百科关于“软件工程”的名词解释:

软件工程是一门研究用工程化方法构建和维护有效的、实用的和高质量的软件的学科。

其中,工程化是方法,是将软件研发的各个链路串接起来的工具。

对于软件“工程化”,个人以为至少应当有如下特点: - 有IDE的支持,负责初始化工程、工程结构组织、debug、编译、打包等工作 - 有固定或者约定的工程结构,规定软件所依赖的不同类别的资源的存放路径甚至代码的写法等 - 软件依赖的资源可能来自软件开发者,也有可能是第三方,工程化需要集成对资源的获取、打包、发布、版本管理等能力 - 和其他系统的集成,如CI系统、运维系统、监控系统等

广泛意义上讲,前端也属于软件工程的范畴。

但前端没有Eclipse、Visual Studio等为特定语言量身打造的IDE。因为前端不需要编译,即改即生效,在开发和调试时足够方便,只需要打开个浏览器即可完成,所以前端一般不会扯到“工程”这个概念。

在很长一段时间里,前端很简单,比如下面简单的几行代码就能够成一个可运行前端应用:

 xml



    <title>webapp</title>
    <link rel="stylesheet" href="app.css">


    <h1>app title</h1>
    <script src="app.js"></script>


但随着webapp的复杂程度不断在增加,前端也在变得很庞大和复杂,按照传统的开发方式会让前端失控:代码庞大难以维护、性能优化难做、开发成本变高。

感谢Node.js,使得JavaScript这门前端的主力语言突破了浏览器环境的限制可以独立运行在OS之上,这让JavaScript拥有了文件IO、网络IO的能力,前端可以根据需要任意定制研发辅助工具。

一时间出现了以Grunt、Gulp为代表的一批前端构建工具,“前端工程”这个概念逐渐被强调和重视。但是由于前端的复杂性和特殊性,前端工程化一直很难做,构建工具有太多局限性。

诚如 张云龙 所言:

前端是一种特殊的GUI软件,它有两个特殊性:一是前端由三种编程语言组成,二是前端代码在用户端运行时增量安装。

html、css和js的配合才能保证webapp的运行,增量安装是按需加载的需要。开发完成后输出三种以上不同格式的静态资源,静态资源之间有可能存在互相依赖关系,最终构成一个复杂的资源依赖树(甚至网)。

所以,前端工程,最起码需要解决以下问题: - 提供开发所需的一整套运行环境,这和IDE作用类似 - 资源管理,包括资源获取、依赖处理、实时更新、按需加载、公共模块管理等 - 打通研发链路的各个环节,debug、mock、proxy、test、build、deploy等

其中,资源管理是前端最需要也是最难做的一个环节。

注:个人以为,与前端工程化对应的另一个重要的领域是前端组件化,前者属于工具,解决研发效率问题,后者属于前端生态,解决代码复用的问题,本篇对于后者不做深入。

在此以开发一个多页面型webapp为例,给出上面所提出的问题的解决方案。

前端开发环境搭建

主要目录结构

 js
- webapp/               # webapp根目录
  - src/                # 开发目录
    + css/              # css资源目录
    + img/              # webapp图片资源目录
    - js/               # webapp js&jsx资源目录
      - components/     # 标准组件存放目录
          - foo/        # 组件foo
            + css/      # 组件foo的样式
            + js/       # 组件foo的逻辑
            + tmpl/     # 组件foo的模板
            index.js    # 组件foo的入口
          + bar/        # 组件bar
      + lib/            # 第三方纯js库
      ...               # 根据项目需要任意添加的代码目录
    + tmpl/             # webapp前端模板资源目录
    a.html              # webapp入口文件a
    b.html              # webapp入口文件b
  - assets/             # 编译输出目录,即发布目录
    + js/               # 编译输出的js目录
    + img/              # 编译输出的图片目录
    + css/              # 编译输出的css目录
    a.html              # 编译输出的入口a
    b.html              # 编译处理后的入口b
  + mock/               # 假数据目录
  app.js                # 本地server入口
  routes.js             # 本地路由配置
  webpack.config.js     # webpack配置文件
  gulpfile.js           # gulp任务配置
  package.json          # 项目配置
  README.md             # 项目说明

这是个经典的前端项目目录结构,项目目结构在一定程度上约定了开发规范。业务开发的同学只需关注src目录即可,开发时尽可能最小化模块粒度,这是异步加载的需要。assets是整个工程的产出,无需关注里边的内容是什么,至于怎么打包和解决资源依赖的,往下看。

本地开发环境

我们使用开源web框架搭建一个webserver,便于本地开发和调试,以及灵活地处理前端路由,以koa为例,主要代码如下:

 js
// app.js
var http = require('http');
var koa = require('koa');
var serve = require('koa-static');

var app = koa();
var debug = process.env.NODE_ENV !== 'production';
// 开发环境和生产环境对应不同的目录
var viewDir = debug ? 'src' : 'assets';

// 处理静态资源和入口文件
app.use(serve(path.resolve(__dirname, viewDir), {
    maxage: 0
}));

app = http.createServer(app.callback());

app.listen(3005, '0.0.0.0', function() {
    console.log('app listen success.');
});

运行node app启动本地server,浏览器输入http://localhost:8080/a.html即可看到页面内容,最基本的环境就算搭建完成。

如果只是处理静态资源请求,可以有很多的替代方案,如Fiddler替换文件、本地起Nginx服务器等等。搭建一个Web服务器,个性化地定制开发环境用于提升开发效率,如处理动态请求、dnsproxy(多用于解决移动端配置host的问题)等,总之local webserver拥有无限的可能。

定制动态请求

我们的local server是localhost域,在ajax请求时为了突破前端同源策略的限制,本地server需支持代理其他域下的api的功能,即proxy。同时还要支持对未完成的api进行mock的功能。

 js
// app.js
var router = require('koa-router')();
var routes = require('./routes');
routes(router, app);
app.use(router.routes());
 js
// routes.js
var proxy = require('koa-proxy');
var list = require('./mock/list');
module.exports = function(router, app) {
    // mock api
    // 可以根据需要任意定制接口的返回
    router.get('/api/list', function*() {
        var query = this.query || {};
        var offset = query.offset || 0;
        var limit = query.limit || 10;
        var diff = limit - list.length;

        if(diff <= 0) {
            this.body = {code: 0, data: list.slice(0, limit)};
        } else {
            var arr = list.slice(0, list.length);
            var i = 0;

            while(diff--) arr.push(arr[i++]);

            this.body = {code: 0, data: arr};
        }
    });

    // proxy api
    router.get('/api/foo/bar', proxy({url: 'http://foo.bar.com'}));
}

webpack资源管理

资源的获取

ECMAScript 6之前,前端的模块化一直没有统一的标准,仅前端包管理系统就有好几个。所以任何一个库实现的loader都不得不去兼容基于多种模块化标准开发的模块。

webpack同时提供了对CommonJS、AMD和ES6模块化标准的支持,对于非前三种标准开发的模块,webpack提供了shimming modules的功能。

受Node.js的影响,越来越多的前端开发者开始采用CommonJS作为模块开发标准,npm已经逐渐成为前端模块的托管平台,这大大降低了前后端模块复用的难度。

在webpack配置项里,可以把node_modules路径添加到resolve search root列表里边,这样就可以直接load npm模块了:

 js
// webpack.config.js
resolve: {
    root: [process.cwd() + '/src', process.cwd() + '/node_modules'],
    alias: {},
    extensions: ['', '.js', '.css', '.scss', '.ejs', '.png', '.jpg']
},
 bash
$ npm install jquery react --save
 js
// page-x.js
import $ from 'jquery';
import React from 'react';

资源引用

根据webpack的设计理念,所有资源都是“模块”,webpack内部实现了一套资源加载机制,这与Requirejs、Sea.js、Browserify等实现有所不同,除了借助插件体系加载不同类型的资源文件之外,webpack还对输出结果提供了非常精细的控制能力,开发者只需要根据需要调整参数即可:

 js
// webpack.config.js
// webpack loaders的配置示例
...
loaders: [
    {
        test: /\.(jpe?g|png|gif|svg)$/i,
        loaders: [
            'image?{bypassOnDebug: true, progressive:true, \
                optimizationLevel: 3, pngquant:{quality: "65-80"}}',
            'url?limit=10000&name=img/[hash:8].[name].[ext]',
        ]
    },
    {
        test: /\.(woff|eot|ttf)$/i,
        loader: 'url?limit=10000&name=fonts/[hash:8].[name].[ext]'
    },
    {test: /\.(tpl|ejs)$/, loader: 'ejs'},
    {test: /\.js$/, loader: 'jsx'},
    {test: /\.css$/, loader: 'style!css'},
    {test: /\.scss$/, loader: 'style!css!scss'},
]
...

简单解释下上面的代码,test项表示匹配的资源类型,loaderloaders项表示用来加载这种类型的资源的loader,loader的使用可以参考using loaders,更多的loader可以参考list of loaders

对于开发者来说,使用loader很简单,最好先配置好特定类型的资源对应的loaders,在业务代码直接使用webpack提供的require(source path)接口即可:

 js
// a.js
// 加载css资源
require('../css/a.css');

// 加载其他js资源
var foo = require('./widgets/foo');
var bar = require('./widgets/bar');

// 加载图片资源
var loadingImg = require('../img/loading.png');

var img = document.createElement('img');

img.src = loadingImg;

注意,require()还支持在资源path前面指定loader,即require(![loaders list]![source path])形式:

 js
require("!style!css!less!bootstrap/less/bootstrap.less");
// “bootstrap.less”这个资源会先被"less-loader"处理,
// 其结果又会被"css-loader"处理,接着是"style-loader"
// 可类比pipe操作

require()时指定的loader会覆盖配置文件里对应的loader配置项。

资源依赖处理

通过loader机制,可以不需要做额外的转换即可加载浏览器不直接支持的资源类型,如.scss.less.json.ejs等。

但是对于css、js和图片,采用webpack加载和直接采用标签引用加载,有何不同呢?

运行webpack的打包命令,可以得到a.js的输出的结果:

 js
webpackJsonp([0], {
    /***/0:
    /***/function(module, exports, __webpack_require__) {

        __webpack_require__(6);

        var foo = __webpack_require__(25);
        var bar = __webpack_require__(26);

        var loadingImg = __webpack_require__(24);
        var img = document.createElement('img');

        img.src = loadingImg;
    },

    /***/6:
    /***/function(module, exports, __webpack_require__) {
        ...
    },

    /***/7:
    /***/function(module, exports, __webpack_require__) {
        ...
    },

    /***/24:
    /***/function(module, exports) {
        ...
    },

    /***/25:
    /***/function(module, exports) {
        ...
    },

    /***/26:
    /***/function(module, exports) {
        ...
    }
});

从输出结果可以看到,webpack内部实现了一个全局的webpackJsonp()用于加载处理后的资源,并且webpack把资源进行重新编号,每一个资源成为一个模块,对应一个id,后边是模块的内部实现,而这些操作都是webpack内部处理的,使用者无需关心内部细节甚至输出结果。

上面的输出代码,因篇幅限制删除了其他模块的内部实现细节,完整的输出请看a.out.js,来看看图片的输出:

 js
/***/24:
/***/function(module, exports) {

    module.exports = "data:image/png;base64,...";

    /***/
}

注意到图片资源的loader配置:

 js
{
    test: /\.(jpe?g|png|gif|svg)$/i,
    loaders: [
        'image?...',
        'url?limit=10000&name=img/[hash:8].[name].[ext]',
    ]
}

意思是,图片资源在加载时先压缩,然后当内容size小于~10KB时,会自动转成base64的方式内嵌进去,这样可以减少一个HTTP的请求。当图片大于10KB时,则会在img/下生成压缩后的图片,命名是[hash:8].[name].[ext]的形式。hash:8的意思是取图片内容hashsum值的前8位,这样做能够保证引用的是图片资源的最新修改版本,保证浏览器端能够即时更新。

对于css文件,默认情况下webpack会把css content内嵌到js里边,运行时会使用style标签内联。如果希望将css使用link标签引入,可以使用ExtractTextPlugin插件进行提取。

资源的编译输出

webpack的三个概念:模块(module)、入口文件(entry)、分块(chunk)。

其中,module指各种资源文件,如js、css、图片、svg、scss、less等等,一切资源皆被当做模块。

webpack编译输出的文件包括以下2种: - entry:入口,可以是一个或者多个资源合并而成,由html通过script标签引入 - chunk:被entry所依赖的额外的代码块,同样可以包含一个或者多个文件

下面是一段entry和output项的配置示例:

 js
entry: {
    a: './src/js/a.js'
},
output: {
    path: path.resolve(debug ? '__build' : './assets/'),
    filename: debug ? '[name].js' : 'js/[chunkhash:8].[name].min.js',
    chunkFilename: debug ? '[chunkhash:8].chunk.js' : 'js/[chunkhash:8].chunk.min.js',
    publicPath: debug ? '/__build/' : ''
}

其中entry项是入口文件路径映射表,output项是对输出文件路径和名称的配置,占位符如[id][chunkhash][name]等分别代表编译后的模块id、chunk的hashnum值、chunk名等,可以任意组合决定最终输出的资源格式。hashnum的做法,基本上弱化了版本号的概念,版本迭代的时候chunk是否更新只取决于chnuk的内容是否发生变化。

细心的同学可能会有疑问,entry表示入口文件,需要手动指定,那么chunk到底是什么,chunk是怎么生成的?

在开发webapp时,总会有一些功能是使用过程中才会用到的,出于性能优化的需要,对于这部分资源我们希望做成异步加载,所以这部分的代码一般不用打包到入口文件里边。

对于这一点,webpack提供了非常好的支持,即code splitting,即使用require.ensure()作为代码分割的标识。

例如某个需求场景,根据url参数,加载不同的两个UI组件,示例代码如下:

 js
var component = getUrlQuery('component');

if('dialog' === component) {
    require.ensure([], function(require) {
        var dialog = require('./components/dialog');
        // todo ...
    });
}

if('toast' === component) {
    require.ensure([], function(require) {
        var toast = require('./components/toast');
        // todo ...
    });
}

url分别输入不同的参数后得到瀑布图:

code_splitting1

code_splitting2

webpack将require.ensure()包裹的部分单独打包了,即图中看到的[hash].chunk.js,既解决了异步加载的问题,又保证了加载到的是最新的chunk的内容。

假设app还有一个入口页面b.html,那麽就需要相应的再增加一个入口文件b.js,直接在entry项配置即可。多个入口文件之间可能公用一个模块,可以使用CommonsChunkPlugin插件对指定的chunks进行公共模块的提取,下面代码示例演示提取所有入口文件公用的模块,将其独立打包:

 js
var chunks = Object.keys(entries);

plugins: [
    new CommonsChunkPlugin({
        name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk
        chunks: chunks,
        minChunks: chunks.length // 提取所有entry共同依赖的模块
    })
],

资源的实时更新

引用模块,webpack提供了require()API(也可以通过添加bable插件来支持ES6的import语法)。但是在开发阶段不可能改一次编译一次,webpack提供了强大的热更新支持,即HMR(hot module replace)

HMR简单说就是webpack启动一个本地webserver(webpack-dev-server),负责处理由webpack生成的静态资源请求。注意webpack-dev-server是把所有资源存储在内存的,所以你会发现在本地没有生成对应的chunk访问却正常。

下面这张来自webpack官网的图片,可以很清晰地说明moduleentrychunk三者的关系以及webpack如何实现热更新的:

HMR

enter0表示入口文件,chunk1~4分别是提取公共模块所生成的资源块,当模块4和9发生改变时,因为模块4被打包在chunk1中,模块9打包在chunk3中,所以HMR runtime会将变更部分同步到chunk1和chunk3中对应的模块,从而达到hot replace。

webpack-dev-server的启动很简单,配置完成之后可以通过cli启动,然后在页面引入入口文件时添加webpack-dev-server的host即可将HMR集成到已有服务器:

 xml
...

    ...
    <script src="http://localhost:8080/__build/vendors.js"></script>
    <script src="http://localhost:8080/__build/a.js"></script>

...

因为我们的local server就是基于Node.js的webserver,这里可以更进一步,将webpack开发服务器以中间件的形式集成到local webserver,不需要cli方式启动(少开一个cmd tab):

 js
// app.js
var webpackDevMiddleware = require('koa-webpack-dev-middleware');
var webpack = require('webpack');
var webpackConf = require('./webpack.config');

app.use(webpackDevMiddleware(webpack(webpackConf), {
    contentBase: webpackConf.output.path,
    publicPath: webpackConf.output.publicPath,
    hot: true,
    stats: webpackConf.devServer.stats
}));

启动HMR之后,每次保存都会重新编译生成新的chnuk,通过控制台的log,可以很直观地看到这一过程:

HMR build

公用代码的处理:封装组件

webpack解决了资源依赖的问题,这使得封装组件变得很容易,例如:

 js
// js/components/component-x.js
require('./component-x.css');

//  https://github.com/okonet/ejs-loader
var template = require('./component-x.ejs');
var str = template({foo: 'bar'});

function someMethod() {}

exports.someMethod = someMethod;

使用:

 js
// js/a.js
import {someMethod} from "./components/component-x";
someMethod();

正如开头所说,将三种语言、多种资源合并成js来管理,大大降低了维护成本。

对于新开发的组件或library,建议推送到npm仓库进行共享。如果需要支持其他加载方式(如RequireJS或标签直接引入),可以参考webpack提供的externals项。

资源路径切换

由于入口文件是手动使用script引入的,在webpack编译之后入口文件的名称和路径一般会改变,即开发环境和生产环境引用的路径不同:

 js
// 开发环境
// a.html
<script src="/__build/vendors.js"></script>
<script src="/__build/a.js"></script>
 js
// 生产环境
// a.html
<script src="http://cdn.site.com/js/460de4b8.vendors.min.js"></script>
<script src="http://cdn.site.com/js/e7d20340.a.min.js"></script>

webpack提供了HtmlWebpackPlugin插件来解决这个问题,HtmlWebpackPlugin支持从模板生成html文件,生成的html里边可以正确解决js打包之后的路径、文件名问题,配置示例:

 js
// webpack.config.js
plugins: [
    new HtmlWebpackPlugin({
        template: './src/a.html',
        filename: 'a',
        inject: 'body',
        chunks: ['vendors', 'a']
    })
]

这里资源根路径的配置在output项:

 js
// webpack.config.js
output: {
    ...
    publicPath: debug ? '/__build/' : 'http://cdn.site.com/'
}

其他入口html文件采用类似处理方式。

辅助工具集成

local server解决本地开发环境的问题,webpack解决开发和生产环境资源依赖管理的问题。在项目开发中,可能会有许多额外的任务需要完成,比如对于使用compass生成sprites的项目,因目前webpack还不直接支持sprites,所以还需要compass watch,再比如工程的远程部署等,所以需要使用一些构建工具或者脚本的配合,打通研发的链路。

因为每个团队在部署代码、单元测试、自动化测试、发布等方面做法都不同,前端需要遵循公司的标准进行自动化的整合,这部分不深入了。

对比&综述

前端工程化的建设,早期的做法是使用Grunt、Gulp等构建工具。但本质上它们只是一个任务调度器,将功能独立的任务拆解出来,按需组合运行任务。如果要完成前端工程化,这两者配置门槛很高,每一个任务都需要开发者自行使用插件解决,而且对于资源的依赖管理能力太弱。

在国内,百度出品的fis也是一种不错的工程化工具的选择,fis内部也解决了资源依赖管理的问题。因笔者没有在项目中实践过fis,所以不进行更多的评价。

webpack以一种非常优雅的方式解决了前端资源依赖管理的问题,它在内部已经集成了许多资源依赖处理的细节,但是对于使用者而言只需要做少量的配置,再结合构建工具,很容易搭建一套前端工程解决方案。

基于webpack的前端自动化工具,可以自由组合各种开源技术栈(Koa/Express/其他web框架、webpack、Sass/Less/Stylus、Gulp/Grunt等),没有复杂的资源依赖配置,工程结构也相对简单和灵活。

附上笔者根据本篇的理论所完成的一个前端自动化解决方案项目模板: webpack-bootstrap

(完)。

该提问来源于开源项目:chemdemo/chemdemo.github.io

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

100条回答

  • weixin_39536728 weixin_39536728 3月前

    如果有这种需求, 建议去看下 FIS3. 这样的需求, 使用 FIS3 可以算是 so easy

    点赞 评论 复制链接分享
  • weixin_39620334 weixin_39620334 3月前

    学习了

    点赞 评论 复制链接分享
  • weixin_40007515 weixin_40007515 3月前

    问题: 如何处理服务端模板的img中的src指向?

    我看楼上已经有提过类似的, 也有回答, 不过只限于纯前端模板解析, 类似用loader去过一遍html之类的, 并不能满足需求, 我这里处理的是服务端的模板, 而家不一定是html, 有可能是ejs, jade之类, 这种情况下img的src好尴尬啊...

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    如果是html或者类html模板(ejs、jade),应该有相应的loader可以做。 不过其他语言的模板如smarty、jsp之类的目前好像没法这么做。

    点赞 评论 复制链接分享
  • weixin_39646695 weixin_39646695 3月前

    关于chunk id的问题,使用manifest和NamedModulesPlugin,是可以解决的。 参考 https://github.com/webpack/webpack/issues/1315 和 https://github.com/kevinrenskers/chunkhash-problem

    点赞 评论 复制链接分享
  • weixin_39736650 weixin_39736650 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39880895 weixin_39880895 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39536728 weixin_39536728 3月前

    你把第一个 app.js 也放到 array 里面呢

    点赞 评论 复制链接分享
  • weixin_39786341 weixin_39786341 3月前

    这样居然OK了!!!感谢!

    点赞 评论 复制链接分享
  • weixin_39536728 weixin_39536728 3月前

    借这里向大家请教一个问题. 我原本写在博客上的. 这里讨论 webpack 多一些, 所以在这里向大家请教.

    我的原文地址 webpack 的 chunkId 是不是一个好的概念

    webpack 的 chunkId 是不是一个好的概念

    webpack 因为有各种各样的 loader, 可以让我们把所有的东西都使用相同的 require 写法进行加载, 同时还可以配合各种 compiler, 让我们从现在就可以享受到 ES6 给我们带来的便利, 所以 webpack 受到了大家的欢迎, 成为 2015 - 2016 特别受欢迎的打包及模块化方案.

    但是在使用 webpack 的时候, 遇到了一个不小的问题, webpack 的 chunkId 对于永久缓存的支持.

    假设我们有这样一个 SPA 页面场景, 有一个路由文件 route.js, 会在此文件中使用 webpack 的 code spliting 写法, 按需加载文件.

    在 route.js 中分别对应三个页面(foo, bar, baz), 按需加载对应的入口文件. 在入口文件中, 会再分别加载各个子页面依赖的文件. - foo.js - dep1.js - dep2.js - bar.js - dep2.js - dep3.js - baz.js - dep1.js - dep3.js

    当我们使用 webpack 进行 build 后, webpack 会给每个文件分配一个 chunkId, 这个 chunkId 是随着 webpack 处理到的文件的数目而进行递增的. 一种结果是类似于下面这样的, 文件后面的括号中是生成的 chunkId 号 - foo.js (1) - dep1.js (4) - dep2.js (5) - bar.js (2) - dep2.js (5) - dep3.js (6) - baz.js (3) - dep1.js (4) - dep3.js (6)

    如果我们需要在foo.js中增加一个依赖 dep4.js, 那么相应的 chunkId 会变成类似于下面这样 - foo.js (1) - dep1.js (4) - dep2.js (5) - dep4.js (6) - bar.js (2) - dep2.js (5) - dep3.js (7) - baz.js (3) - dep1.js (4) - dep3.js (7)

    我们只增加(或删除)了一个文件, 却导致了多个文件的 chunkId 变化, 这样就导致多个文件的内容发生了变化. 那么当重新发布后, 其实有的页面的内容根本不需要变化, 但是仅仅是因为 chunkId 变化, 而导致需要重新下载这些文件, 使得没法使用浏览器已经缓存的文件.

    如果你的页面有几十个, 每次添加或者删除一个文件后, 都会导致几乎所有的文件的浏览器缓存失效.

    我们期望的结果是, 每当我们修改一个文件后, 只有依赖此文件的文件会更新, 而其它的文件不会发生变化. 当重新发布后, 只会去重新下载那些已经被更新的文件, 这样我们就可以做到类似于增量更新的效果.

    但是 webpack 采用的 chunkId 的概念好像没办法避免这样的问题. 如果将 chunkId 换成依赖文件的路径, 是不是可以减少这种问题的发生呢?

    在项目中把 webpack 换成了 FIS3, 解决了这个问题. 因为在FIS中, 依赖就是使用的文件路径, 所以文件位置不变, 依赖的id也是不变的. 依赖合并的问题, 我是把一些全局依赖使用 __inline 的方式合并到一个文件中. 其它的分散依赖则使用 require.async 进行按需加载

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    纠正下,webpack生成的是moduleId不是chunkid。你说的这个的确是个问题,在很多群里我们都讨论过了,目前这可以说是webpack的一个bug,暂时无解。

    我跟你的想法一样,也在考虑fis来取代webpack,因为fis团队发布了支持npm的模块fis3-hook-node_modules,不过我还没时间尝试,你可以研究下。

    点赞 评论 复制链接分享
  • weixin_39617215 weixin_39617215 3月前

    真心赞 mark下 慢慢看

    点赞 评论 复制链接分享
  • weixin_39634878 weixin_39634878 3月前

    mark 准备好好学webpack 不知道现在这个工具就目前被项目采纳的程度怎么样? 知道的前辈 说说 大概都有哪些坑 我等后生 可以少跳点

    点赞 评论 复制链接分享
  • weixin_39579127 weixin_39579127 3月前

    慢慢看

    点赞 评论 复制链接分享
  • weixin_39570530 weixin_39570530 3月前

    https://github.com/diurnalist/chunk-manifest-webpack-plugin

    点赞 评论 复制链接分享
  • weixin_39536728 weixin_39536728 3月前

    谢谢. 已经把构建工具切换到FIS3了. 整体用下来感觉比webpack更简单.

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    fis3对npm的支持怎么样了

    点赞 评论 复制链接分享
  • weixin_39536728 weixin_39536728 3月前

    目前还没用hook npm.😰

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    好吧 不考虑npm的话用哪个无所谓啦

    点赞 评论 复制链接分享
  • weixin_39518530 weixin_39518530 3月前

    webstorm 不算为前端打造的IDE? 那么强的调试能力和智能化提示~

    点赞 评论 复制链接分享
  • weixin_39518530 weixin_39518530 3月前

    《善用软件-提高编程效率》http://dwz.cn/3J1gle

    另外visual studio code 听说也不错~

    点赞 评论 复制链接分享
  • weixin_39849894 weixin_39849894 3月前

    ,想了解一下,你们lint这一块,fis是怎么处理的?

    我用fis的相关lint npm包,可以使用

    
    fis3 release -l
    

    但是有两个问题, - 当文件写的有问题,确实会有提示,但是它也把文件给输出去了 - fis 有没有纯命令来检验代码的

    毕竟像grunt/gulp,只要配置一个指令就OK了。。

    之前有在群里面聊过,有些人提到用webstorm来做代码校验,但我觉得工具上能不能再加一个保障,毕竟不能强迫别人一定要用webstorm。

    点赞 评论 复制链接分享
  • weixin_39536728 weixin_39536728 3月前

    我没有使用fis来进行lint检查, 我是单独用的eslint来执行lint的. 只不过是选了不同的检查时机. 我是在 git commit的时候进行强制检查, 检查不通过, 直接不允许commit. 这样, 团队中的人都一定会通过lint了

    可以看我写的这篇 http://isay.me/2016/05/use-eslint-force-code-style-check.html

    点赞 评论 复制链接分享
  • weixin_39849894 weixin_39849894 3月前

    团队中有用git,也有用svn,git的团队可以保证用你的写法,其实也有一个pre-commit,这个玩意来保证。。但是svn的前端团队,似乎没有好的办法了?

    点赞 评论 复制链接分享
  • weixin_39978282 weixin_39978282 3月前

    和 推荐使用 https://github.com/typicode/husky , 目前我们团队使用这个, 另外在推荐一个 commitizen 设置提交模板很方便,自己定制一下。参见PPT

    点赞 评论 复制链接分享
  • weixin_39536728 weixin_39536728 3月前

    我基本上没用过svn, 这块可能知道的不多.

    这样的话, 只好研究一下配置fis的lint了 😰

    或者大家都自觉的自己检查一下, 只不过这个有点不现实, 因为有的人根本不知道什么是lint...

    当然最好还是促进大家转到git上面去了 😁

    点赞 评论 复制链接分享
  • weixin_39536728 weixin_39536728 3月前

    我目前也在使用了commitizen, 只不过只是使用了commitizen 的 cz-conventional-changelog 规范, 没有使用这个工具进行提交. 项目中配置了commitizen命令, 只不过是我自己为了写commit log时省事加的. 团队其他人只要遵守commit log规则就好了.

    这是我的另一个 git hook, 和上面的 pre-commit进行lint的搭配使用. 检查失败了, 直接引导去看阮一峰写的博文去了😄

    你说的这个工具我当时看了. 感觉自己配置的更灵活吧 😁

     sh
    #!/bin/bash
    
    msg=$(head -n 1 $1)
    
    if echo $msg | grep -iqE "^Merge "; then
      exit 0
    fi;
    
    if echo $msg | grep -iqE "^Revert "; then
      exit 0
    fi;
    
    if echo $msg | grep -iqE "^(feat|fix|docs|style|refactor|perf|test|chore)(\([^()]{1,}\)){0,1}: .{1,}"; then
      exit 0
    fi;
    
    echo
    echo -e "\033[41mINVALID COMMIT MSG:\033[0m Does not match \"<type>(<scope>): <subject>\" !"
    echo
    cat <<eof available types and what it mean are list here feat: a new feature fix: bug fix docs: documentation only changes style: that do not affect the meaning of code formatting missing semi-colons etc refactor: change neither fixes or adds perf: improves performance test: adding tests chore: to build process auxiliary tools libraries such as generation eof echo read http: exit></eof></subject></scope></type>
    点赞 评论 复制链接分享
  • weixin_39978282 weixin_39978282 3月前

    是的 但是 commitizen 这个东西也可以自己定制,也很方便 我们自己定制的 https://github.com/ShuyunXIANFESchool/shuyun-cz-conventional-changelog

    点赞 评论 复制链接分享
  • weixin_39849894 weixin_39849894 3月前

    ,你上面发的git链接,应该是类似于我之前写的一篇文章:

    pre-commit

    点赞 评论 复制链接分享
  • weixin_39712705 weixin_39712705 3月前

    Mark

    点赞 评论 复制链接分享
  • weixin_39647412 weixin_39647412 3月前

    mark~~

    点赞 评论 复制链接分享
  • weixin_39640417 weixin_39640417 3月前

    有的公司的cdn域名是分开的,就比如: js的cdn域名:js.xxcdn.com.cn css的cdn域名是:css.xxcdn.com.cn images的cdn域名是:img.xxcdn.com.cn

    在这种情况下,webpack的一个publicPath配置貌似不能区分,所以在用html-webpack-plugin插件注入模板的时候,所有注入的文件的路径名就只有一个cdn域名,即publicPath里边配置的那个cdn域名。

     javascript
    ...
    output:{
           path:APP_PATH,
           filename:'[name].js',
           publicPath:'http://xx.xxcdn.com.cn/'
    },
    ...
    
    plugins:[
        new ExtractTextPlugin("[name].css"),
        new HtmlWebpackPlugin({
            filename:"test.html",
            template:path.resolve(APP_PATH,"./html/test.html"),
            inject:"body"
        })
    ],
    ...
    

    这么生成的页面就会是这种:

     html
    
    
    
    
    
        ...
        <title>这是一个测试的页面</title>  
        <link href="http://xx.xxcdn.com.cn/test.css" rel="stylesheet">
    
    
        <p>
            Hello word!
        </p>
        <script type="text/javascript" src="http://xx.xxcdn.com.cn/test.js"></script>
    
    

    但我其实想要的是css被注入为:

     html
    
    <link href="http://css.xxcdn.com.cn/test.css" rel="stylesheet">
    
    

    js被注入为:

     html
    <script type="text/javascript" src="http://xx.xxcdn.com.cn/test.js"></script>
    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    是的 publicPath不支持函数,这个是个缺陷 难不成国外没这种分cdn域名的需求哈哈哈

    点赞 评论 复制链接分享
  • weixin_39888180 weixin_39888180 3月前

    路过,好精彩

    点赞 评论 复制链接分享
  • weixin_39955700 weixin_39955700 3月前
    • webapp/ # webapp根目录
    • src/ # 开发目录
      • css/ # css资源目录
      • img/ # webapp图片资源目录
      • login/ # 登陆页图品目录
        • a.png
        • b.png
      • home/ # 首页图片目录
      • c.png
    • assets/ # 编译输出目录,即发布目录
      • js/ # 编译输出的js目录
      • img/ # webapp图片资源目录
      • login/ # 登陆页图品目录
        • a.png
        • b.png
      • home/ # 首页图片目录
      • c.png

    请问输出这样的图片目录, webpack 中怎么配置

    点赞 评论 复制链接分享
  • weixin_39575565 weixin_39575565 3月前

    路过,再回头看看

    点赞 评论 复制链接分享
  • weixin_39518530 weixin_39518530 3月前

    看了两天的webpack,觉得还是配置太复杂了,不如gulp好用~

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    webpack和gulp不是同一类型的东西,前者是打包工具后者可以看成是一个任务调度器,拿在一起比较不合适

    点赞 评论 复制链接分享
  • weixin_39849894 weixin_39849894 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39897749 weixin_39897749 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39845347 weixin_39845347 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39912250 weixin_39912250 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39752800 weixin_39752800 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39878247 weixin_39878247 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39757265 weixin_39757265 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39533361 weixin_39533361 3月前

    好久没来了,才看到你在上述(9月)的回复,你建议我可以试试scriptJS来异步加载所需模块。 问题是这样加载到的模块,是不具备webpack模块引用的功能的,它们不存在webpackJsonp队列中,不能互相引用,说白了只能适用全局的常规插件,但想走模块规范形式就不行了。有解决方案么

    同问,在进行CMD模块迁移中卡在这里了。:dizzy_face:

    点赞 评论 复制链接分享
  • weixin_39940957 weixin_39940957 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39519554 weixin_39519554 3月前

    mark 文章和评论都值得好好看下

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    -zone 你们说的是AMD吧?amd的加载可以参考这里: https://webpack.github.io/docs/amd.html 还有个例子: https://github.com/webpack/webpack/tree/master/examples/mixed

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    webpack只会加载在线的模块(在本地没有已经下载完成的情况下),最多是走HTTP缓存,需要存储到local里的情况需要自己去做判断。

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    第一个问题,可以在HtmlWebpackPlugin前使用html-loader,参考webpack-config.js#L68,webpack-seed这个项目已经fix了你说这个问题。

    第二个问题,参考下file-loaderpath选项,在输出结果里配置不同的路径。

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    参考上一条comment

    点赞 评论 复制链接分享
  • weixin_39892311 weixin_39892311 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39714763 weixin_39714763 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39820173 weixin_39820173 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39624864 weixin_39624864 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39614704 weixin_39614704 3月前

    你说的这个问题,最近让我大伤脑筋。生无可恋脸 不知道过了这么久,你有没有好的解决方案!

    点赞 评论 复制链接分享
  • weixin_39853131 weixin_39853131 3月前

    好东西

    点赞 评论 复制链接分享
  • weixin_39786341 weixin_39786341 3月前

    你好,请教个问题。

    一个模块是不是不能作为entry两次?如下

    
    entry: {
      index: 'app.js',
      'index-polyfill': ['app.js', 'polyfill.js']
    }
    

    打包报错:

    
    Module not found: Error: a dependency to an entry point is not allowed
    

    不知道这个问题有没有解决方案呢?

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    这种好像做不到,filename项只支持字符串。

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    有例子吗 我没这样做过哎 一般无需关注webpack输出的结果。

    点赞 评论 复制链接分享
  • weixin_39786341 weixin_39786341 3月前

    对啊,有没有线上的例子或者教程呀?有点急,在线等哈!

    点赞 评论 复制链接分享
  • weixin_39814482 weixin_39814482 3月前
    
    {
        entry: {
            'common/a': "./a",
            'page/b': "./b"
        },
        output: {
            path: path.join(__dirname, "dist"),
            filename: "[name].entry.js"
        }
    }
    

    具体还可参考 http://webpack.github.io/docs/multiple-entry-points.html 页面底部的三种示例

    点赞 评论 复制链接分享
  • weixin_39786341 weixin_39786341 3月前

    这里最终打包的都是到了 /yourpath/dist/下面而已吧,都是同级的。我需要的是根据入口文件的路径来确定输出文件的路径哦

    点赞 评论 复制链接分享
  • weixin_39814482 weixin_39814482 3月前

    我发的code中,/就会自动生成文件夹目录。我项目都是这么用的

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    你的示例明显达不到他的需求啦 filename如果支持函数就可以

    点赞 评论 复制链接分享
  • weixin_39814482 weixin_39814482 3月前

    我这也是多个entry,每个entry自己指定目录,达不到他需求?

    点赞 评论 复制链接分享
  • weixin_39786341 weixin_39786341 3月前

    恩。多个entry是可以达到的,但是output不行。我是希望output可以为每个文件指定不同的目录

    点赞 评论 复制链接分享
  • weixin_39814482 weixin_39814482 3月前
    
    {
        entry: {
            'common/a': "./a",
            'page/b': "./b"
        },
        output: {
            path: path.join(__dirname, "dist"),
            filename: "[name].entry.js"
        }
    }
    

    输出的文件:

    
    /dist
       /common
          a.entry.js
       /page
          b.entry.js
    

    我不知道你们还有啥需求

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    额 好吧 看出来了

    点赞 评论 复制链接分享
  • weixin_39786341 weixin_39786341 3月前

    好腻害!!谢谢两位!

    点赞 评论 复制链接分享
  • weixin_39591455 weixin_39591455 3月前

    你好,请原谅在这里问一个关于webpack的问题。

    需求是“单页应用&按需加载”。实践过程中遇到了问题。下面是问题描述:

    目录结构:

     html
    /app
      |-- components/
      |           |-- header.js
      |-- pages/
      |        |-- index.js
      |        |-- about.js
      |        |-- login.js
      |-- entry.js
    

    描述: - pages/index.jspages/about.js都依赖components/header.js - pages/login.js不依赖components/header.js - entry.js是入口文件,根据URL按需加载pages/*.js

     js
    // 监听path路径,按需加载
    window.addEventListener('popstate', function () {
      switch (path) {
        case '/':
          require.ensure(['./pages/index.js']);
          break;
        case '/about':
          require.ensure(['./pages/about.js']);
          break;
      }
    });
    

    结果:生成了两个按需加载的chunks,但两个chunks都包含了components/header.js。即存在两份。

    期望结果:components/header.js作为一个独立的chunk。这样可以减少代码量(代价是多了请求数量)

    webpack能实现我期望结果的需求吗?

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    可以在打包的时候将components/header.js作为独立的入口:

     js
    {
        entry: {
            'components/header': 'components/header.js',
        }
    }
    

    在html里需要手动引用。

    点赞 评论 复制链接分享
  • weixin_39786341 weixin_39786341 3月前

    按照这个例子有个问题:

     js
    {
        entry: {
            a: './js/pages/a.js',
            b: './js/pages/b.js',
            c: './js/pages/c.js',
            d: './js/pages/d.js'
        },
        ...
        plugins: [
            // 提取公共依赖模块,这种适合提取共享的基础库,如jquery、underscore等
            new CommonsChunkPlugin('lib', ['a', 'b', 'c', 'd'], 4),
            // 提取a、b、c中至少两个共同依赖的模块
            new CommonsChunkPlugin('common-abc', ['a', 'b', 'c'], 2),
            // 提取a、d共同依赖的模块
            new CommonsChunkPlugin('common-ad', ['a', 'd']);
        ]
    }
    

    生成的lib.js和common-abc.js里面都定义了window["webpackJsonp"],如下面的代码。他们会出现覆盖的情况。

     js
    var parentJsonpFunction = window["webpackJsonp"];
    /******/    window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
             ...
             ...
    

    请问怎样解决呢?

    ////////////////////////////////////////////////

    找到问题了

     js
     // 提取公共依赖模块,这种适合提取共享的基础库,如jquery、underscore等
     new CommonsChunkPlugin('lib', ['a', 'b', 'c', 'd'], 4),
     // 提取a、b、c中至少两个共同依赖的模块
     new CommonsChunkPlugin('common-abc', ['a', 'b', 'c', 'lib'], 2),
    

    把lib加到common-abc的chunks数组里面就好了,但是在页面引用的时候一定得先引用common-abc.js,再引用lib.js。。。这样虽然可以用,但是有点违背依赖的直觉

    点赞 评论 复制链接分享
  • weixin_39604897 weixin_39604897 3月前

    例子编译后,为啥编译运行之后,assets下a.html,b.html,c.html都缺少 webpackJsonp 这个的定义。看了下,只在common-bc的js文件下存在,但没有自动引用

    点赞 评论 复制链接分享
  • weixin_39804603 weixin_39804603 3月前

    点赞 评论 复制链接分享
  • weixin_39698217 weixin_39698217 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39938855 weixin_39938855 3月前

    好久没来了,才看到你在上述(9月)的回复,你建议我可以试试scriptJS来异步加载所需模块。 问题是这样加载到的模块,是不具备webpack模块引用的功能的,它们不存在webpackJsonp队列中,不能互相引用,说白了只能适用全局的常规插件,但想走模块规范形式就不行了。有解决方案么

    点赞 评论 复制链接分享
  • weixin_39646695 weixin_39646695 3月前

    mark

    点赞 评论 复制链接分享
  • weixin_39956110 weixin_39956110 3月前

    下面这句话基本理解,但有些疑问。

    想象在某种理想情况下,静态资源构建的结果不是合并文件(资源内嵌除外),而是分析资源之间的依赖并且有工具去识别这种依赖关系,在资源下载的时候支持combine......

    如下几个问题 引用1:分析资源之间的依赖并且... - 问1: 是否可以通过webpack 生成的stats来进行分析?

    引用2:且有工具去识别这种依赖关系,在资源下载的时候支持combine - 问2:可否简单理解这个功能提供类似反响代理、nginx_concat_module的功能(或者是纯前端的实现,应该叫做加载器),如果是实际环境下的话可能需要支持云处理(或者生成一大堆可能出现的情况) - 问3:以上引用里面会存在一个问题,此方案并未解决“重复下载公共文件”。(目前这种方案不可行的地方在两处 1.http下发多个文件, 2.这多个文件的缓存。还有一种方案是前端需要有一个加载器来合并这些请求,且拿到combind的数据后再拆分存储在browser中,按现有方案,只能用localstorage类的)

    update:看完 fouber的文章后清晰多了,欢迎回复交流

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    你基本跟我想一块儿去了,我就是这个意思。

    构建工具和加载工具分开,构建工具负责解析资源依赖并生成依赖表。 加载工具就可以多种多样了,ng合并是最简单原始的一种,另外前端同样需要实现一个加载框架,就是用于处理缓存和重复加载的问题,其实我期望的是形成统一的资源表标准和加载器标准,有浏览器统一来实现,这样才算完美。

    参考这里的回复

    点赞 评论 复制链接分享
  • weixin_39890327 weixin_39890327 3月前

    请教有关webpack的两个问题: 1. 对于在模板中用 img src="*" 引入的图片,怎么加md5戳并替换新的路径; 2. 怎么将不同页面的图片加md5戳后输出到对应的目录下 例如:page1.html用到的图片输出到page1目录下,page2.html用到的图片输出到page2目录下

    webpack.config.js 相关配置如下: module: {   loaders: [     {       test: /.(jpe?g|png|gif)$/,       loader: 'url?limit=8192&name=images/[name]-[hash:8].[ext]'     }   ] }, plugins: [   new HtmlWebpackPlugin({     template: 'page1/page1.html',     filename: 'page1/index.html',     inject: 'body',     chunks: ['libs', 'page1/page1-entry']   }),   new HtmlWebpackPlugin({     template: 'page2/page2.html',     filename: 'page2/default.html',     inject: 'body',     chunks: ['libs', 'page2/page2-entry']    }) ]

    点赞 评论 复制链接分享
  • weixin_39883129 weixin_39883129 3月前

    路过,好精彩

    点赞 评论 复制链接分享
  • weixin_39599097 weixin_39599097 3月前
    • 看了又看,还得多看看,谢谢
    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    第一个问题,html中引用的图片会自动修改路径的和加hash戳,只要你配置了图片loader 第二个问题,url-loader提供了资源路径作为占位符,即[path],可以根据path项配置输出路径

    点赞 评论 复制链接分享
  • weixin_39828198 weixin_39828198 3月前

    Mark

    点赞 评论 复制链接分享
  • weixin_39817122 weixin_39817122 3月前

    一般webapp是有一个物理页面,然后根据hash或者url加载不同的“虚拟页面”来展现应用,大致的资源关系如下:

    webpack

    从下往上看,应用一般有一个入口模块,比如app,这个入口模块根据url来动态决定异步加载某个页面(P₁-P₄),而每个页面并不是孤岛资源,它们还会依赖其他组件,组件与组件之间可能还有共享的基础库依赖。

    基于这样的资源依赖树结构,webpack是怎么解决资源合并和按需加载的?感觉静态分析打包不能很好的处理按需和请求合并问题,要么每个chunk冗余合并,要么多个chunk串行加载

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    webpack可以通过CommonsChunkPlugin插件来对公共依赖模块进行提取:

     js
    {
        entry: {
            a: './js/pages/a.js',
            b: './js/pages/b.js',
            c: './js/pages/c.js',
            d: './js/pages/d.js'
        },
        ...
        plugins: [
            // 提取公共依赖模块,这种适合提取共享的基础库,如jquery、underscore等
            new CommonsChunkPlugin('lib', ['a', 'b', 'c', 'd'], 4),
            // 提取a、b、c中至少两个共同依赖的模块
            new CommonsChunkPlugin('common-abc', ['a', 'b', 'c'], 2),
            // 提取a、d共同依赖的模块
            new CommonsChunkPlugin('common-ad', ['a', 'd']);
        ]
    }
    

    更多可以参考multiple commons chunks

    静态分析打包是事先生成chunk,如果需要消除模块冗余,只能自行配置来提取,所以webpack也是有局限性和学习成本。

    对于按需加载,比如你提到的这种场景,p1-p4就当做chunk来加载了:

     js
    // app.js
    
    switch(hash) {
        // index#p1
        case 'p1':
            require.ensure([], function() {
                require('p1');
            });
            break;
    
        // index#p2
        case 'p2':
            require.ensure([], function() {
                require('p2');
            });
            break;
    
        ...
        // 依次类推
    }
    

    打包完成之后,p1到p4会生成名为[hash].chunk.js的一系列文件。

    点赞 评论 复制链接分享
  • weixin_39817122 weixin_39817122 3月前

    我应该是理解webpack的做法的,只是你可能没有注意到我的例子是一个怎样的陷阱:

    webpack可以通过CommonsChunkPlugin插件来对公共依赖模块进行提取

    单独看我例子中的a、b依赖c的情况,这个说法是正确的。

    对于按需加载,比如你提到的这种场景,p1-p4就当做chunk来加载了

    单独看我例子中的app加载p1-p4,这个说法也是正确的。

    但是,把二者结合起来,就不是那么回事了。。。

    因为p1-p4各自为一个chunk,其结果就是p1-p4不用处理,就是文件本身就行了;而ab抽取公共依赖c,也等价于三个文件不用处理,自然就是“c为ab的公共chunk”,在静态分析的模式下,这个例子的最优合并结果居然是不合并!

    现实中这样的例子其实更多,而且会更复杂,静态分析面对大工程最终的结果往往是要么因为其局限性而根本配不出来合理的方案,要么因为配置太多维护成本过高而变成一个bundle的情况,没有真正的优化空间,那些“有公共依赖抽取插件从而进行优化”的假设基本形同虚设。

    点赞 评论 复制链接分享
  • weixin_39817122 weixin_39817122 3月前

    看过很多号称使用了webpack的项目,基本上无外乎这么几种最终运行效果: - 图省事,一个大bundle(呵呵) - 一个页面一个chunk,公共资源冗余重复合并在各个chunk中(呵呵) - 刚好页面彼此很独立,完全没有公共资源(算你走运)

    正如 在我的blog底下留言说到的

    以Webpack目前对WebApp架构支持的完善程度,它只差一步就能实现WebApp和WebSite(暂且这么叫,就是传统的服务端模板生成页面的意思)的通吃,只要它实现了生成资源表的API(前提是Webpack认同 资源表+资源加载框架 这种理念),然后,如果业界出现了某种资源表的事实标准,那么就一切就完美了~

    我觉得这种认识是早晚的,因为优势太明显了吧。。。

    此外,我还想吐槽webpack的一些设计: 1. require的返回结果只有三种情况: - 返回文件uri——资源定位 - 返回文件内容(文本或base64)——资源内嵌 - 什么都不返回,只是在表中记录资源依赖关系(如果认可)——依赖声明

    除了以上三种情况,前端构建不再需要多余的规则了。而返回文件内容时到底该返回文本还是文件base64是可以被唯一确定的,只要给出被require的文件就知道该返回哪种了。所以我觉得webpack还可以在精进一步概念。 2. 这个require标识可以扩展到html和css。有些技术选型的情况下我们可能也需要在html或者css中标记依赖或者资源定位,比如模板中可能直接写一个img src定位资源(当然,你可以准换成js中定位然后模板传值给html)。

    在三种语言中分别提供资源定位、资源内嵌、依赖声明的构建标识,采用表+资源加载框架优化加载,以上这两点构成了fis的核心设计理念,不可否认webpack是目前最接近fis的构建工具

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    这个例子很特殊啊,webpack资源合并的最优结果是不合并,但是webpack内部已经自动“标识”了资源之间的依赖关系(包括加hash戳)。所以这并不矛盾,生成的chunk的最小单元就是一个资源文件。

    想象在某种理想情况下,静态资源构建的结果不是合并文件(资源内嵌除外),而是分析资源之间的依赖并且有工具去识别这种依赖关系,在资源下载的时候支持combine......

    所以“资源表+资源加载框架”这个概念我算是理解通了,看来要去学习下FIS。

    不过关于webpack require 的设计:

    1.require就是一个标识符,至于资源如何加载(或依赖标记)的,看对应的loader怎么实现。 2.凡是通过require标识加载的资源,对于资源中使用了浏览器直接可识别的外部资源加载API,webpack都会预先使用loader加载,比如某个模板里边使用img src引用了图片,编译时图片最终也是交给image loader去加载的,css同理。

    点赞 评论 复制链接分享
  • weixin_39938855 weixin_39938855 3月前

    为方便同步、打包,都走的本地文件形式,在webpack里引用非本地文件只能通过externals来实现么,但这种情况下希望实现懒加载又不行了(require.ensure内还是必须使用的本地模块或提前下载好了的模块)。

    像我司大部分移动端项目都会把公共模块(CMD模式)投放到分发的CDN上(服务器配置的资源过期时间很短也不建议修改之,webpack 的文件hash形式不太适用),客户端第一次则下载并存入localstorage,下次则直接从本地取,这种缓存模式走 webpack 是否有办法实现?

    这样就忍痛舍弃了seajs的依赖就近模式(比如可以使用require.async在需要的时候才下载某模块),需要一口气先把全部需要的公共模块都下载下来(就是我第一段提及的问题)。但在旧项目我们先引入了seajs才下载模块,而在webpack 需要走externals配置(即在webpack打包后的bundle下载之前先下载),这时候是还没有模块加载器的,就悲剧了。。。

    不知道对于我描述的问题是否有好的解决思路,我暂时也只想着留给后续的新项目用新的开发模式来走webpack,但这样新旧两种开发模式,开发和维护成本都有点高(无奈脸)

    点赞 评论 复制链接分享
  • weixin_39902545 weixin_39902545 3月前

    参考下这个issue

    webpack是一个模块打包器,不是文件加载器,它只能加载本地磁盘上的文件。对于你们的情况,CDN的公共lib可以采用scriptjs加载,因为本地还需要判断localstorage,所以需要先判断local再加载,代码类似:

     js
    
    if(localStorage.getItem('lib-content')) {
        // todo
    } else {
        var $script = require("scriptjs");
        $script('http://cdn.yourdomain.com/lib/jquery.js', function() {
            // todo
        });
    }
    

    而这段代码就可以放到入口文件里使用webpack打包。

    对于公共业务module,个人觉得还是在开发时存放到本地比较好,开发完可以发布到npm或者公司内部模块管理平台。

    点赞 评论 复制链接分享
  • weixin_39789646 weixin_39789646 3月前

    谁能跟我说说,issue能收藏吗,小白白路过。。

    点赞 评论 复制链接分享
  • weixin_39998541 weixin_39998541 3月前

    -lin 你在这里回复了之后,以后有新的回复时你会收到通知的

    点赞 评论 复制链接分享
  • weixin_39786341 weixin_39786341 3月前

    大牛,有个问题请教。我正在使用webpack做构建,我有多个entry,这时output的目录能指定多个,达到一对一的效果吗?就是'/src/common/a.js' --> '/dist/common/a.bundle.js','/src/page/b.js' --> '/dist/page/a.bundle.js'。请问大牛这种现在webpack能实现吗?

    点赞 评论 复制链接分享
  • weixin_39814482 weixin_39814482 3月前

    可以的,参考官方文档吧

    点赞 评论 复制链接分享
  • weixin_39814482 weixin_39814482 3月前

    先占位,学习学习

    点赞 评论 复制链接分享
  • weixin_39786341 weixin_39786341 3月前

    赞!持续关注!敢问作者大概啥时候会出下篇?

    点赞 评论 复制链接分享
  • weixin_39613089 weixin_39613089 3月前

    占位,学习

    点赞 评论 复制链接分享