开始使用 Webpack 2

l123456.. 8年前
   <p><a href="/misc/goto?guid=4959734218901757282" rel="nofollow,noindex">Webpack 2 的文档完成</a> 就会推出 beta 版本。如果你已经知道怎么去配置它,那么你无需等待文档就可以使用 Webpack 2 了。</p>    <h3>什么是 Webpack?</h3>    <p>最初的时候,Webpack 仅是一个 JavaScript 的模块打包工具。随着 Webpack 日渐流行,逐渐演变成了前端代码的管理工具(不论是人为故意还是社区推动的)。</p>    <p><img src="https://simg.open-open.com/show/8095163d2fc15cf24cc860b5e553b5ff.png"></p>    <p>以前的运行方式是:标记文件、样式文件和 JavaScript 文件都被分割开的。你必须要独立管理每一个文件,使得所有文件可以正确的运行。</p>    <p>像 Gulp 这样的构建工具可以操作许多不同的预处理器和编译器,不过基本上所有的工作都看成是把一个源文件作为输入,经过处理后生成一个编译后的输出文件。Gulp 做的工作更像是一个任务接着一个任务的进行的,没有从全局的管理上考虑。这就会加重开发者的负担:在生产环境下,开发者需要知道任务在哪里结束,然后需要正确地把所有任务都组装在一起。</p>    <p>Webpack 尝试询问一个大胆的问题来减轻开发者的负担:是否有一个开发过程可以处理所引用到的依赖?我们是否可以简单地用某种方式去写代码,而构建程序会去管理最终所必需使用到的代码?</p>    <p><img src="https://simg.open-open.com/show/f186a5916882596f2ee217b892c478c5.png"></p>    <p>Webpack 的方式是:如果 Webpack 知道了它,它会把你实际用到的东西打包在构建产物里。</p>    <p>如果过去几年你已经混迹在 web 社区里,你应该知道解决问题更好的方式是:用 JavaScript 构建。Webpack 可以通过 JavaScript 传递依赖让构建过程更加容易。Webpack 的设计真正厉害之处并不在于代码的管理,而是它的管理层 100% 都是由 JavaScript 写的(Node 特性)。Webpack 使得你有能力在写 JavaScript 时候对系统全局有更好的把握和掌控。</p>    <p>换句话说:你不需要为 Webpack 写任何代码,只需要给项目写代码,然后Webpack会自动运行(当然,一些配置文件是少不了的)</p>    <p>简而言之,如果你已经与以下任意一个问题纠结过:</p>    <ul>     <li> <p>加载依赖项</p> </li>     <li> <p>在生产版本中包含了未使用的 CSS 或者 JS</p> </li>     <li> <p>意外多次加载同一个库</p> </li>     <li> <p>遇到 CSS 和 JavaScript 作用域的问题</p> </li>     <li> <p>在 JavaScript 里使用一个例如 Node/Bower 这样很好的管理系统,或者依赖一个可怕疯狂的配置来正确使用那些模块</p> </li>     <li> <p>需要更好地优化生产出资源,但又怕破坏到某些事情</p> </li>    </ul>    <p>那么,你可以从 Webpack 获得非常多的帮助。Webpack 可以轻易地解决上面问题,因为它可以通过 JavaScript 管理你的依赖和加载顺序,而不是通过你的开发者头脑。最棒的部分?Webpack 甚至可以直接地运行在服务端,这意味着你可以使用 Webpack 构建 <a href="/misc/goto?guid=4959734218980508722" rel="nofollow,noindex">渐进加强</a> 的网站。</p>    <h3>第一步</h3>    <p>在这篇指导里,我们会使用 <a href="/misc/goto?guid=4958999003582682021" rel="nofollow,noindex">Yarn</a> ( brew install yarn ) 来替代 npm ,不过这个取决于你,它们都可以做到同样的事情。在项目文件夹下,我们会在终端窗口里执行以下的指令,在全局和本地项目里添加 Webpack 2。</p>    <pre>  <code class="language-javascript">yarn global add webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.10  yarn add --dev webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.10</code></pre>    <p>在项目根目录下声明一个 webpack 配置文件 webpack.config.js :</p>    <pre>  <code class="language-javascript">'use strict';    const webpack = require("webpack");    module.exports = {    context: __dirname + "/src",    entry: {      app: "./app.js",    },    output: {      path: __dirname + "/dist",      filename: "[name].bundle.js",    },  };</code></pre>    <p>注意: ___dirname_ 是指你的项目根目录</p>    <p>你知道 Webpack 是怎么知道项目如何运行的吗?它是通过读取你的代码来获知这一信息的,Webpack 基本上会像以下这个流程进行工作:</p>    <ol>     <li> <p>从 context 文件夹开始</p> </li>     <li> <p>寻找 entry 上的文件名</p> </li>     <li> <p>读取内容。每当遇到 import ( <a href="/misc/goto?guid=4959734219101505053" rel="nofollow,noindex">ES6</a> ) 或者 require() (Node) 依赖项时,它会解析这些代码,并且打包到最终构建里。接着它会不断递归搜索实际需要的依赖项,直到它到达了“树”的底部。</p> </li>    </ol>    <ol>     <li>从上一步接着,Webpack 把所有东西打包到 output.path 的文件夹里,并使用 output.filename 命名( [name] 表示使用 entry 项的 key)</li>    </ol>    <p>所以我们的 src/app.js 文件看起来就像这样(假设我们之前执行的是 yarn add --dev moment ):</p>    <pre>  <code class="language-javascript">'use strict';  import moment from 'moment';  var rightNow = moment().format('MMMM Do YYYY, h:mm:ss a');  console.log( rightNow );  // "October 23rd 2016, 9:30:24 pm"</code></pre>    <p>执行</p>    <pre>  <code class="language-javascript">webpack -p</code></pre>    <p>注意: <strong> _p_ </strong> 表示 “生产” 模式,这时候的输出文件会被 uglifies/minifies。</p>    <p>它会输出一个 dist/app.bundle.js 文件,同时在控制台里打出当前日期时间的日志。注意,Webpack 会自动知道 'moment' 模块是指向哪里(即使在目录里,你有一个 moment.js 文件,默认情况下 Webpack 还是会优先使用 moment Node 模块)。</p>    <h3>多文件工作</h3>    <p>你可以通过修改 entry 对象来指定任意数量的输入或者输出点。</p>    <p>多个文件打包在一起</p>    <pre>  <code class="language-javascript">'use strict';    const webpack = require("webpack");    module.exports = {    context: __dirname + "/src",    entry: {      app: ["./home.js", "./events.js", "./vendor.js"],    },    output: {      path: __dirname + "/dist",      filename: "[name].bundle.js",    },  };</code></pre>    <p>根据数组顺序,文件全部会被一起打包在 dist/app.bundle.js 里。</p>    <p>多个文件,多个输出</p>    <pre>  <code class="language-javascript">const webpack = require("webpack");    module.exports = {    context: __dirname + "/src",    entry: {      home: "./home.js",      events: "./events.js",      contact: "./contact.js",    },    output: {      path: __dirname + "/dist",      filename: "[name].bundle.js",    },  };</code></pre>    <p>或者,你可以选择把你的应用打包成多个 JS 文件,上面的例子就会被打包成以下三个文件: dist/home.bundle.js 、 dist/events.bundle.js , 和 dist/contact.bundle.js 。</p>    <p>先进的自动打包</p>    <p>如果你将应用分开打包到多个 output 文件里(如果你的应用有非常多的 JS 不需要在前期加载,这样做是非常有效的),这里面是有可能会出现冗余代码的,因为 Webpack 是独立解析每个文件的依赖的。幸运的是,Webpack 已经有个内置的 <em>CommonsChunk</em> 插件处理这个问题:</p>    <pre>  <code class="language-javascript">module.exports = {    // …    plugins: [      new webpack.optimize.CommonsChunkPlugin({        name: "commons",        filename: "commons.js",        minChunks: 2,      }),    ],  // …  };</code></pre>    <p>现在,纵观所有 output 文件,如果你有任何模块需要加载 2 次或者更多次(由 minChunks 设置),这些模块会被打包在 commons.js 里,那么你就可以让它在客户端里缓存起来。当然,这会导致产生一个额外的头部请求,但是你可以阻止客户端多次下载同一个库。在许多场景下,会有一个速度的净收益。</p>    <h3>开发环境</h3>    <p>Webpack 实际上有自己的开发服务器,因此无论你是想开发一个静态网站还是做一个前端的原型,它都可以满足你。如果想这样开发,只需要添加一个 devServer 对象到 webpack.config.js 就可以:</p>    <pre>  <code class="language-javascript">module.exports = {    context: __dirname + "/src",    entry: {      app: "./app.js",    },    output: {      filename: "[name].bundle.js",      path: __dirname + "/dist/assets",      publicPath: "/assets",            // New    },    devServer: {      contentBase: __dirname + "/src",  // New    },  };</code></pre>    <p>在终端执行:</p>    <pre>  <code class="language-javascript">webpack-dev-server</code></pre>    <p>现在你的服务器跑在 localhost:8080 。 <em>注意 script 标签里的 `</em> /assets_ 路径由 <em>output.publicPath</em> ` 决定——你可以随便命名(如果你需要一个 CDN,这就非常有用了)_。</p>    <p>你无需刷新浏览器,Webpack 会热加载任何 JavaScript 的改变。但是, <strong>任何对</strong> **webpack.config.js** 文件的改变都需要重启服务器才会生效。</p>    <h3>全局调用方法</h3>    <p>需要使用在全局作用域下的函数?只需要在 webpack.config.js 的 output.library 进行简单的设置:</p>    <pre>  <code class="language-javascript">module.exports = {    output: {      library: 'myClassName',    }  };</code></pre>    <p>它会把你的打包文件捆绑在 window.myClassName 实例上。当设置了声明作用域时,你可以在入口处调用这个方法(更多的设置可在 <a href="/misc/goto?guid=4959734219184231131" rel="nofollow,noindex">文档</a> 中查询)。</p>    <h3>Loaders</h3>    <p>直到现在,我们仅仅处理了 JavaScript 文件。从 JavaScript 开始是很重要的,因为 JavaScript 是 Webpack 唯一识别的语言。但实际上,只要是用 JavaScript 传递的文件,我们都可以使用 <em>Loaders</em> 来处理任何种类的文件。</p>    <p>loader 可以是像 Sass 这样的预处理器,也可以是像 Babel 这样的编译器。在 NPM 里,它们通常被命名为 *-loader ,例如: sass-loader 或者 babel-loader 。</p>    <p>Babel + ES6</p>    <p>如果你想在项目里通过 <a href="/misc/goto?guid=4958865492110783321" rel="nofollow,noindex">Babel</a> 使用 ES6,我们首先需要本地安装合适的 loaders。</p>    <pre>  <code class="language-javascript">yarn add --dev babel-loader babel-core babel-preset-es2015</code></pre>    <p>然后把它添加到 webpack.config.js ,以便于让 Webpack 知道在什么地方使用它。</p>    <pre>  <code class="language-javascript">module.exports = {    // …    module: {      rules: [        {          test: /\.js$/,          use: [{            loader: "babel-loader",            options: { presets: ["es2015"] }          }],        },          // Loaders for other file types can go here      ],    },    // …  };</code></pre>    <p>Webpack 的老用户需要注意一点:Loaders 的核心理念还是保持一致的,但是它的语法有所改善。直到文档完成了,我们才能知道准确合适的语法。</p>    <p>/\.js$/ 这条正则表达式会去搜索以 .js 后缀结束的文件,并通过 Babel 加载。Webpack 使用正则表达式来让你可以完整的控制所有文件,而无需限定在某一个文件扩展名,又或者假定你是使用某种方式进行文件组织的。</p>    <p>CSS + Style Loader</p>    <p>如果我们只想加载应用所需的 CSS,我们同样可以利用 Webpack 做到。如果我们直接在 index.js 文件里引入 CSS:</p>    <pre>  <code class="language-javascript">import styles from './assets/stylesheets/application.css';</code></pre>    <p>我们会遇到以下的错误: You may need an appropriate loader to handle this file type 。记住,Webpack 只能够理解 JavaScript,所以我们需要安装合适的 loader:</p>    <pre>  <code class="language-javascript">yarn add --dev css-loader style-loader</code></pre>    <p>然后给 webpack.config.js 添加一条规则:</p>    <pre>  <code class="language-javascript">module.exports = {    // …    module: {      rules: [        {          test: /\.css$/,          use: ["style-loader", "css-loader"],        },        // …      ],    },  };</code></pre>    <p>Loaders 会根据数组的逆序运行,也就是说 css-loader 会跑在 style-loader 前面。</p>    <p>你可能会注意到,在生产构建下,CSS 是会被打包到 JavaScript 里的, style-loader 会把你的样式写在 Style 标签里。刚开始看起来是有点奇怪,但是随着深入思考你会觉的这是有意义的。因为你可以在一些网络连接上节省一个头部请求的时间,并且只要你用了 JavaScript 来加载 Dom 节点,这可以有效地减少它自身的 <a href="/misc/goto?guid=4959734219303605108" rel="nofollow,noindex">FOUC</a> 。</p>    <p>你也会发现 Webpack 黑盒的构建产物已经自动地通过将文件打包在一起,把所有的 @import 查询都解析了(而不是依靠 CSS 默认会导致浪费头部请求和加载资源非常慢的 import 功能)。</p>    <p>从 JS 里加载 CSS 是非常神奇的, <strong>因为这样你可以用新的方式将 CSS 模块化。</strong> 也就是说你可以通过 button.js 加载 button.css ,而如果 button.js 实际上没有用到,对应的 CSS 也不会被调价到生产构建中。如果你是坚持使用面向组件 CSS 实践方法的,例如:SMACSS 或者 BEM,你会体会到将 CSS 和 标记语言 + JavaScript 更加紧密联系的价值。</p>    <p>CSS + Node 模块</p>    <p>我们可以使用 Webpack 里的 ~ 前缀来引入 Node 模块。假如我们执行了 yarn add normalize.css ,那么就可以这么用:</p>    <pre>  <code class="language-javascript">@import "~normalize.css";</code></pre>    <p>这样可以充分利用 NPM 管理第三方样式文件的版本,还可以避免了我们进行复制黏贴。更长远地看,用 Webpack 打包 CSS 相比使用 CSS 默认的 import 有明显的优势,这是因为它可以为客户端消除头部请求以及缓慢的加载时间。</p>    <p>更新:本章节和下一章节为了解答对通过 CSS 模块来简单引入 Node 模块的疑惑,以及提高文章的准确性进行了更新。感谢 <a href="/misc/goto?guid=4959734219386968745" rel="nofollow,noindex">Albert Fernández</a> 的帮助。</p>    <p>CSS 模块</p>    <p>你可能已经听说过 <a href="/misc/goto?guid=4958965462431997440" rel="nofollow,noindex">CSS 模块</a> ,当你使用 JavaScript 构建 Dom 节点,它可以不出意料地运行地非常好,它通过 JavaScript 加载,然后把你的 CSS 类神奇地设置好层叠作用域。如果你准备使用 CSS 模块,可以用 css-loader 来打包( yarn add --dev css-loader ):</p>    <pre>  <code class="language-javascript">module.exports = {    // …    module: {      rules: [        {          test: /\.css$/,          use: [            "style-loader",            { loader: "css-loader", options: { modules: true } }          ],        },        // …      ],    },  };</code></pre>    <p>_注意:我们通过使用对象字面量的语法给 _css-loader_ 传递选项。你也可以用一个字符串作为简写来传递,这样就会使用默认的选项,就像我们用 _style-loader_ 这样。_</p>    <p>在用 Node 模块引入 CSS 模块的时候,需要注意的是你实际上可以抛弃 ~ 来直接引入。但是,当你 @import CSS 时,你可能会遇到一个构建错误。如果你得到是类似 "can’t find ..." 这样的错误,你可以尝试给 webpack.config.js 添加一个 resolve 对象,这样会让 Webpack 对预定的模块顺序有更好的理解。</p>    <pre>  <code class="language-javascript">const path = require("path");    module.exports = {    //…    resolve: {      modules: [path.resolve(__dirname, "src"), "node_modules"]    },  };</code></pre>    <p>我们规定了源文件目录,这样的做话 Webpack 可以解析地更加好。Webpack 会根据模块名首先去寻找我们的源文件目录,然后是安装的 Node 模块目录里(也就是分别用 "src" 和 "node_modules" 来代替源文件目录和 Node 模块目录)。</p>    <p>Sass</p>    <p>需要用 Sass?没问题,可以这么安装:</p>    <pre>  <code class="language-javascript">yarn add --dev sass-loader node-sass</code></pre>    <p>然后加一条规则:</p>    <pre>  <code class="language-javascript">module.exports = {    // …    module: {      rules: [        {          test: /\.(sass|scss)$/,          use: [            "style-loader",            "css-loader",            "sass-loader",          ]        }        // …      ],    },  };</code></pre>    <p>这时候你的 JavaScript 文件里就可以用 import 来引用 .scss 或者 .sass 文件,接着 Webpack 会完成它该做的事情。</p>    <p>CSS 分开打包</p>    <p>也许你需要处理渐进加强;又或许因为一些别的原因,你需要把 CSS 独立出来一个文件。我们无需修改任何代码,只用简单的在配置里把 extract-text-webpack-plugin 代替 style-loader 。下面的 app.js 就是例子:</p>    <pre>  <code class="language-javascript">import styles from './assets/stylesheets/application.css';</code></pre>    <p>安装本地插件(我们需要 2016 年 10 月的 bata 版本)...</p>    <pre>  <code class="language-javascript">yarn add --dev extract-text-webpack-plugin@2.0.0-beta.4</code></pre>    <p>添加到 webpack.config.js :</p>    <pre>  <code class="language-javascript">const ExtractTextPlugin = require("extract-text-webpack-plugin");    module.exports = {    // …    module: {      rules: [        {          test: /\.css$/,          loader:  ExtractTextPlugin.extract({            loader: 'css-loader?importLoaders=1',          }),        },        // …      ]    },    plugins: [      new ExtractTextPlugin({        filename: "[name].bundle.css",        allChunks: true,      }),    ],  };</code></pre>    <p>现在,执行 webpack -p ,你就可以在 output 目录里发现 app.bundle.css 文件。接着就是非常轻松地像平常一样在 HTML 里添加一个 tag 引入就可以了。</p>    <p>HTML</p>    <p>正如你想到的一样,Webpack 也有一个叫 <a href="/misc/goto?guid=4959734219503938097" rel="nofollow,noindex">html-loader</a> 的插件。但是当我们准备用 JavaScript 去加载 HTML 的时候,这里有一点需要注意,我无法通过一个单独的例子就为你下一步做的事情准备好,因为这可能分出无数各种各样地方法。通常来说,你可能是为了在 <a href="/misc/goto?guid=4958968605084997344" rel="nofollow,noindex">React</a> 、 <a href="/misc/goto?guid=4958834801588021319" rel="nofollow,noindex">Angular</a> 、 <a href="/misc/goto?guid=4958857378973396767" rel="nofollow,noindex">Vue</a> ,又或者 <a href="/misc/goto?guid=4958340976610047764" rel="nofollow,noindex">Ember</a> 这些大型框架里使用类似 <a href="/misc/goto?guid=4959734219693562939" rel="nofollow,noindex">JSX</a> 、 <a href="/misc/goto?guid=4959734219790525307" rel="nofollow,noindex">Mustache</a> ,或者 <a href="/misc/goto?guid=4958341606252679125" rel="nofollow,noindex">Handlebars</a> 这样的 JavaScript-flavored 标记语言。又或者你只是在使用一个 HTML 的预处理器,例如: <a href="/misc/goto?guid=4959734219903446347" rel="nofollow,noindex">Pug</a> (Jade 的前身)或者 <a href="/misc/goto?guid=4959734219991332487" rel="nofollow,noindex">Haml</a> 。再者你可能仅仅只是想按字面意思一样把源文件里的 HTML 丢到构建目录里。无论你想做什么,我都是无法假定。</p>    <p>结束语:你 <em>可以</em> 用 Webpack 加载标记语言文件,但是你需要决定自身的项目架构,这是 Webpack 和我都无法为你做这个决定的。不过根据上述例子作为参照,然后在 NPM 上搜索正确的 loaders,这些对于你来说应该是足够用的了。</p>    <h3>用模块的方式思考</h3>    <p>为了最大程度的使用 Webpack,你必须用模块化、可复用性以及可独立处理的思维方式去思考,让每个模块把各自负责的事情做好。这意味着类似下面这样的文件:</p>    <pre>  <code class="language-javascript">└── js/      └── application.js   // 300KB of spaghetti code</code></pre>    <p>会变成这样:</p>    <pre>  <code class="language-javascript">└── js/      ├── components/      │   ├── button.js      │   ├── calendar.js      │   ├── comment.js      │   ├── modal.js      │   ├── tab.js      │   ├── timer.js      │   ├── video.js      │   └── wysiwyg.js      │      └── application.js  // ~ 1KB of code; imports from ./components/</code></pre>    <p>最后的结果出来的是非常简洁,可复用的代码。每个独立的组件通过 import 来引入依赖,再通过 export 来暴露公共接口给其他模块。把上面这些特性与 Babel 和 ES6 结合,你可以使用 <a href="/misc/goto?guid=4958972612389441616" rel="nofollow,noindex">JavaScript Classes</a> 来实现更好的模块化,而不需要考虑运行作用域。</p>    <p> </p>    <p>来自:http://www.zcfy.cc/article/getting-started-with-webpack-2-thinking-in-code-2110.html</p>    <p> </p>