Skip to content

教你一步步从零构建webpack开发多页面环境 #27

Open
@riskers

Description

@riskers
Owner


使用 webpack 已经将近一年了,期间用它构建过4、5个项目,踩过一些坑,现在用自己的理解记录下来。

我现在教你如何一步一步搭建 webpack 开发的多页面项目。本文项目地址在 https://github.com/riskers/generate-pages-tutorial

首先需要安装:

git clone https://github.com/riskers/generate-pages-tutorial

这里使用的是 webpack 1.x,webpack 2 见文末

注意每一步的 webpack.config.jspageage.json

一、基本 JavaScript 模块的处理

cd 1_multi_pages
npm install
npm run build

查看 webpack.config.js 可以其实就是配置多个 entry 而已,可以看到 dist 下生成编译好的文件:

|--- dist
        |--- page1
                |--- main.js
        |--- page2
                |--- main.js

这里的目录层级和 entry 中的模块名(page1/mainpage2/main)对应。

打开 page1.htmlpage2.html 就可以看到我们的js模块生效了。现在进入下一步!

二、CSS 模块的处理

通过上一步,我们已经解决了 JavaScript 模块的问题,而页面中还有 CSS 。webpack 默认是只处理 JavaScript 的,所以我们要引入 css-loaderstyle-loader 来处理 CSS。

cd 2_css
npm install
npm run build

CSS

{
    test: /\.css$/,
    loaders: ['style', 'css']
}

loader 是专门处理某些模块的处理器。webpack 只能处理 js ,为了能够处理 CSS ,就需要 css-loader;而为了能够在页面中插入 CSS ,还需要 style-loader

打开 page1.html 就可以看到 css 生效了。

less

{
    test: /\.less$/,
    loaders: ['style', 'css', 'less']
}

如果使用的是 less ,就需要安装 lessless-loader

打开 page2.html 就可以看到 less 生效了。

sass

{
    test: /\.scss$/,
    loaders: ['style', 'css', 'sass']
}

如果使用的是 sass ,就需要安装 node-sasssass-loader

打开 page3.html 就可以看到 less 生效了。

postcss

module: {
    loaders: [
        {
            test: /\.css$/,
            include: ROOT + '/src/page4',
            loaders: ['style', 'css', 'postcss']
        }
    ]
},
postcss: function() {
    return [autoprefixer]
}

如果使用的是 sass ,就需要安装 postcss-loader,这里是以 autoprefixer 为例。

打开 page4.html 就可以看到 less 生效了。

生成 CSS 文件

以上方法都是用 JS 生成 CSS,但是实际上,我们需要的是 CSS 文件,可以使用 extract-text-webpack-plugin 来解决。

打开 page5.html 可以看到效果

三、reload

上面2节我们已经掌握 JS 模块和 CSS 模块的处理,并且能够让 CSS 独立生成文件了,现在我们觉得每次修改代码然后 build 再刷新浏览器这个过程实在太慢了,而且也没必要每修改一行代码,就生成新文件,这是构建速度慢的主要原因。

webpack-dev-server 是 webpack 自带的一个开发服务器,支持热替换、代理等等功能。

cd 3_reload
npm install
npm run dev

打开 0.0.0.0:8888/page1.html ,你就可以看到页面了。而且无论你修改 main.jsstyle.csstpl/page1.html 都会让浏览器自动刷新。

这里使用了:

  • html-webpack-plugin: 在页面中自动注入 js 和 css
  • html-webpack-harddisk-plugin: 每次修改 pages/tpl 内文件时,会自动在 pages/html 内生成对应的文件
  • raw-loader: 可以 require html 文件,做到每修改一次 tpl 文件,浏览器自动刷新一次页面

还有一点值得注意,因为 reload 功能是开发时才需要的,所以我们在 build 的时候要把这部分剔除,cross-envDefinePlugin 的配合可以做到这点。

  • cross-env 能够不分系统地在全局注入变量,下面这条命令就是将 DEV 注入 ENV 环境变量
cross-env ENV=DEV webpack-dev-server --inline --hot --quiet --progress --content-base pages/html  --host 0.0.0.0 --port 8888
  • DefinePlugin 将 process.env.ENV 这个环境变量注入 ENV
new webpack.DefinePlugin({
    'ENV': JSON.stringify(process.env.ENV)
})
  • main.js 中就可以区分是开发环境还是生产环境了:
if(ENV == 'DEV') {
    require('pages/html/page1.html')    
}

四、ES2015 && babel

如果你要在 webpack 中配置 ES2015 的开发环境,需要 babel 的帮助:

  • babel-core
  • babel-loader
  • babel-preset-es2015
  • babel-preset-stage-0
  • babel-plugin-add-module-exports
  • babel-plugin-transform-runtime
cd 4_es2015
npm install
npm run dev

然后在 webpack.config.js 中:

{
    test: /\.js$/,
    loader: "babel",
    exclude: /node_modules/
}

注意 exclude: /node_modules/ 很重要,否则 babel 可能会把 node_modules 中所有模块都用 babel 编译一遍!

并且,在根目录下新建 .babelrc

{
    presets: [
        "es2015",
        "stage-0"
    ],
    plugins: [
        "transform-runtime",
        "add-module-exports"
    ]   
}

然后我们就可以写我们可爱的 ES2015 了:

import './style.css'
import { log } from '../common/index.js'

五、引入库

cd 5_library
npm install
npm run dev

CommonsChunkPlugin

CommonsChunkPlugin 是 webpack 自带的插件,能够把公有模块提取出来:

plugins: [
    new webpack.optimize.CommonsChunkPlugin('common','common.js') 
]

HtmlWebpackPlugin 中加入 common/index.js 模块,我们就可以看到 common/index.js 模块被提取到了 common.js 中。否则的话,page1/mainpage2/main 中都会打包 common/index.js

externals

实际开发中,我们还会在页面使用 <script> 引入一些常用库,比如 jQuery ,那么我们需要

// 当在 js 中 require jQuery 时,实际上是指向 `window.jQuery`
externals: {
    jQuery: 'window.jQuery'
}

然后我们就可以在 page1/main.js 中使用 jQuery 了:

import $ from 'jQuery'
$('body')
    .append('<p>this is jQuery render</p>')
    .css('color', '#FFF')

ProvidePlugin

当然,对于 jQuery 这种每个页面都会使用到的库来说,每次都要 import $ from 'jQuery' 显得很不优雅。可以这样配置:

plugins: [
    new webpack.ProvidePlugin({
        $: 'jquery'
    })
]

这样就可以像 page2/main.js 中那样了:

$('body')
    .append('<p>this is jQuery render</p>')
    .css('color', '#3f3f3f')

六、代理

经过上面几个步骤,我们基本上已经完成了 webpack 的开发环境搭建,但是 pages 里全是静态页面,而我们后端实际上使用的可能是 PHP、Python 甚至是 Node 渲染的动态页面。

现在我们要解决的问题是把现有的 webpack 开发环境和已有的后端环境结合起来,我们这里使用的是 webpack-dev-serverproxy 功能:

devServer: {
    proxy: {
        '*': {
            target: 'http://localhost:8000'
        }
    }
}
cd 6_proxy
npm install
npm run dev

为了模拟一个后端环境,这里使用 PHP 自带的 server 在 8000 端口开启服务:

php -S 127.0.0.1:8000 -t ./pages/html

然后打开 http://0.0.0.0:8888/page1.php 就可以看到页面被 webpack-dev-server 代理过来了。你可以在 pages/html 下得到正式的已经配置好资源路径的页面。

执行 npm run build 后,你会在 pages/html 下得到相应 CDN 地址的资源路径,CDN 地址可以在 npm scripts 下配置:

"scripts": {
    "build": "cross-env CDN=http://cdn.a.com webpack"
}

七、团队协作

到此为止,一个 webpack 搭建的多页面开发环境已经完成了,还有一些与 webpack 无关的话题要注意一下。

ESLint

ESLint 是代码检查工具,这里不多介绍了。如果你使用的是 es2015 ,记得安装 babel-eslint 就好。

pre-commit

pre-commit 是一个很好用的工具,你可以使用它强制性地让团队成员在 commit 代码前执行任何命令(ESLint、测试等等)

"scripts": {
    "lint": "eslint app/src/ app/stylesheets"
},
"precommit": [ "lint" ]

注意使用时要先安装:

node node_modules/pre-commit/install.js

八、一个脚手架模板

我还建立了项目 https://github.com/riskers/generate-pages ,它包括了最最基本的 webpack 开发多页面骨架,感兴趣的也可以看看。由其是 map.js 和 模板文件的映射,这种思路应该可以帮你少写很多代码。

最重要的是,赶紧动手开始使用 webpack 吧!

希望这份教程帮到了你 -_-

20170215 更新

最近,webpack 2 终于正式发布了,之前大概看过他的 beta 版本,但是没有正式发布之前我始终没有去实践,这两天得空把 https://github.com/riskers/generate-pages-tutorial 上的代码更新到了 v2 版本。总体来说,webpack2 默认支持 module、提供 tree shaking 是2个比较让我在意的新功能,其他的以后再细细研究了。


向我捐助 | 关于我 | 工作机会


Activity

Ale-cc

Ale-cc commented on Jan 20, 2017

@Ale-cc

挺实用的

gengxuelei

gengxuelei commented on Jan 20, 2017

@gengxuelei

这个也不错,只是不完善:
https://github.com/cooking-demo/multiple-pages-vue

jl1014171068

jl1014171068 commented on Jan 20, 2017

@jl1014171068

应该是postcss-loader 而不是 post-loader 吧?

riskers

riskers commented on Jan 20, 2017

@riskers
OwnerAuthor

@jl1014171068 已修改 谢谢

F3n67u

F3n67u commented on Feb 4, 2017

@F3n67u
cd 2_css
npm install
npm run build

这一步我这边build失败了,输出如下:

> @ build E:\demo\generate-pages-tutorial\2_css
> webpack

Hash: 7a71525e1fe34bec0080
Version: webpack 1.14.0
Time: 600ms
        Asset     Size  Chunks             Chunk Names
page1/main.js  1.75 kB       0  [emitted]  page1/main
page2/main.js  11.9 kB       1  [emitted]  page2/main
page3/main.js  11.9 kB       2  [emitted]  page3/main
page4/main.js  1.75 kB       3  [emitted]  page4/main
page5/main.js  1.75 kB       4  [emitted]  page5/main
   [0] ./src/page1/main.js 95 bytes {0} [built] [1 error]
   [0] ./src/page2/main.js 96 bytes {1} [built]
   [0] ./src/page3/main.js 96 bytes {2} [built]
   [0] ./src/page5/main.js 95 bytes {4} [built] [1 error]
   [0] ./src/page4/main.js 95 bytes {3} [built] [1 error]
   [1] ./src/page1/style.css 0 bytes [built] [failed]
   [2] ./src/common/index.js 51 bytes {0} {1} {2} {3} {4} [built]
   [9] ./src/page4/style.css 0 bytes [built] [failed]
  [10] ./src/page5/style.css 0 bytes [built] [failed]
    + 6 hidden modules

ERROR in ./src/page1/style.css
Module parse failed: E:\demo\generate-pages-tutorial\2_css\src\page1\style.css Unexpected token (1:5)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (1:5)
    at Parser.pp$4.raise (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:2221:15)
    at Parser.pp.unexpected (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:603:10)
    at Parser.pp.semicolon (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:581:61)
    at Parser.pp$1.parseExpressionStatement (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:966:10)
    at Parser.pp$1.parseStatement (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:730:24)
    at Parser.pp$1.parseTopLevel (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:638:25)
    at Parser.parse (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:516:17)
    at Object.parse (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:3098:39)
    at Parser.parse (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack\lib\Parser.js:902:15)
    at DependenciesBlock.<anonymous> (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack\lib\NormalModule.js:104:16)
    at DependenciesBlock.onModuleBuild (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack-core\lib\NormalModuleMixin.js:310:10)
    at nextLoader (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack-core\lib\NormalModuleMixin.js:275:25)
    at E:\demo\generate-pages-tutorial\2_css\node_modules\webpack-core\lib\NormalModuleMixin.js:259:5
    at Storage.finished (E:\demo\generate-pages-tutorial\2_css\node_modules\enhanced-resolve\lib\CachedInputFileSystem.js:38:16)
    at E:\demo\generate-pages-tutorial\2_css\node_modules\graceful-fs\graceful-fs.js:78:16
    at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:445:3)
 @ ./src/page1/main.js 1:0-22

ERROR in ./src/page4/style.css
Module parse failed: E:\demo\generate-pages-tutorial\2_css\src\page4\style.css Unexpected token (1:5)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (1:5)
    at Parser.pp$4.raise (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:2221:15)
    at Parser.pp.unexpected (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:603:10)
    at Parser.pp.semicolon (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:581:61)
    at Parser.pp$1.parseExpressionStatement (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:966:10)
    at Parser.pp$1.parseStatement (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:730:24)
    at Parser.pp$1.parseTopLevel (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:638:25)
    at Parser.parse (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:516:17)
    at Object.parse (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:3098:39)
    at Parser.parse (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack\lib\Parser.js:902:15)
    at DependenciesBlock.<anonymous> (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack\lib\NormalModule.js:104:16)
    at DependenciesBlock.onModuleBuild (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack-core\lib\NormalModuleMixin.js:310:10)
    at nextLoader (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack-core\lib\NormalModuleMixin.js:275:25)
    at E:\demo\generate-pages-tutorial\2_css\node_modules\webpack-core\lib\NormalModuleMixin.js:259:5
    at Storage.finished (E:\demo\generate-pages-tutorial\2_css\node_modules\enhanced-resolve\lib\CachedInputFileSystem.js:38:16)
    at E:\demo\generate-pages-tutorial\2_css\node_modules\graceful-fs\graceful-fs.js:78:16
    at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:445:3)
 @ ./src/page4/main.js 1:0-22

ERROR in ./src/page5/style.css
Module parse failed: E:\demo\generate-pages-tutorial\2_css\src\page5\style.css Unexpected token (1:5)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (1:5)
    at Parser.pp$4.raise (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:2221:15)
    at Parser.pp.unexpected (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:603:10)
    at Parser.pp.semicolon (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:581:61)
    at Parser.pp$1.parseExpressionStatement (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:966:10)
    at Parser.pp$1.parseStatement (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:730:24)
    at Parser.pp$1.parseTopLevel (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:638:25)
    at Parser.parse (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:516:17)
    at Object.parse (E:\demo\generate-pages-tutorial\2_css\node_modules\acorn\dist\acorn.js:3098:39)
    at Parser.parse (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack\lib\Parser.js:902:15)
    at DependenciesBlock.<anonymous> (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack\lib\NormalModule.js:104:16)
    at DependenciesBlock.onModuleBuild (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack-core\lib\NormalModuleMixin.js:310:10)
    at nextLoader (E:\demo\generate-pages-tutorial\2_css\node_modules\webpack-core\lib\NormalModuleMixin.js:275:25)
    at E:\demo\generate-pages-tutorial\2_css\node_modules\webpack-core\lib\NormalModuleMixin.js:259:5
    at Storage.finished (E:\demo\generate-pages-tutorial\2_css\node_modules\enhanced-resolve\lib\CachedInputFileSystem.js:38:16)
    at E:\demo\generate-pages-tutorial\2_css\node_modules\graceful-fs\graceful-fs.js:78:16
    at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:445:3)
 @ ./src/page5/main.js 1:0-22

之后我尝试了将loader部分改为如下代码:

loaders: [
	{
		test: /\.css$/,
		loaders: ['style', 'css']
	},
	{
		test: /\.less$/,
		loaders: ['style', 'css', 'less']
	},
	{
		test: /\.scss$/,
		loaders: ['style', 'css', 'sass']
	},
]

或者

loaders: [
	{
		test: /\.less$/,
		loaders: ['style', 'css', 'less']
	},
	{
		test: /\.scss$/,
		loaders: ['style', 'css', 'sass']
	},
	{
		test: /\.css$/,
		loaders: ['style', 'css', 'postcss']
	}
]

或者

loaders: [
	{
		test: /\.less$/,
		loaders: ['style', 'css', 'less']
	},
	{
		test: /\.scss$/,
		loaders: ['style', 'css', 'sass']
	},
	{
		test: /\.css$/,
		loader: extractCSS.extract('style', 'css')
	}
]

编译都能通过。
所以,我怀疑是loader的include属性导致的。

麻烦楼主答疑解惑,谢谢

riskers

riskers commented on Feb 4, 2017

@riskers
OwnerAuthor

@F3n67u 因为我是 Mac 没办法测试 windows 下的情况, Mac 下是没问题的。

至于 include 属性,它的值就是指定使用这个 loaders 的范围而已,我这里这么写,只是想把 less、sass、postcss 这些情况全部列出来,显得很乱了。实际应用不会有这种情况的。

xvinc

xvinc commented on Feb 5, 2017

@xvinc

{
test: /.css$/,
loaders: ['style', 'css'],
// include: ROOT + '/src/page1/'
include: path.resolve(__dirname, 'src/page1'),
},
{
test: /.less$/,
loaders: ['style', 'css', 'less']
},
{
test: /.scss$/,
loaders: ['style', 'css', 'sass']
},
{
test: /.css$/,
loaders: ['style', 'css', 'postcss'],
//include: ROOT + '/src/page4'
include: path.resolve(__dirname, 'src/page4'),
},
{
test: /.css$/,
loader: extractCSS.extract('style', 'css'),
//include: ROOT + 'src/page5'
include: path.resolve(__dirname, 'src/page5'),
}

xvinc

xvinc commented on Feb 5, 2017

@xvinc

图片打包也加个

riskers

riskers commented on Feb 6, 2017

@riskers
OwnerAuthor

@xvinc 好的 后续加上

374632897

374632897 commented on Feb 9, 2017

@374632897

第一部分项目地址那里多加了个句号。

cdflove9426

cdflove9426 commented on Feb 12, 2017

@cdflove9426

2_css 的文件,原先也是发生了ERROR in ./src/page1/style.css
我是这样配置的

module: {
		loaders: [
			{
				test: /\.css$/,
				//include: ROOT + '/src/page1',
				include:path.join(__dirname,'src/page1'),
				loaders: ['style', 'css']
			},
			{
				test: /\.less$/,
				loaders: ['style', 'css', 'less']
			},
			{
				test: /\.scss$/,
				loaders: ['style', 'css', 'sass']
			},
			{
				test: /\.css$/,
				// include: ROOT + '/src/page4',
				include:path.join(__dirname,'src/page4'),
				loaders: ['style', 'css', 'postcss']
			},
			{
				test: /\.css$/,
				// include: ROOT + '/src/page5',
				include:path.join(ROOT,'src/page5'),
				loader: extractCSS.extract('style', 'css')
			}
		]
	}

成功运行

        Asset      Size  Chunks             Chunk Names
 page1/main.js   11.8 kB       0  [emitted]  page1/main
 page2/main.js     12 kB       1  [emitted]  page2/main
 page3/main.js     12 kB       2  [emitted]  page3/main
 page4/main.js   12.3 kB       3  [emitted]  page4/main
 page5/main.js   1.71 kB       4  [emitted]  page5/main
page5/main.css  55 bytes       4  [emitted]  page5/main
   [0] ./src/page3/main.js 96 bytes {2} [built]
   [0] ./src/page1/main.js 95 bytes {0} [built]
   [0] ./src/page5/main.js 95 bytes {4} [built]
   [0] ./src/page4/main.js 95 bytes {3} [built]
   [0] ./src/page2/main.js 96 bytes {1} [built]
   [5] ./src/common/index.js 51 bytes {0} {1} {2} {3} {4} [built]
    + 12 hidden modules
Child extract-text-webpack-plugin:
        + 2 hidden modules
547377507

547377507 commented on Apr 4, 2017

@547377507

你好我测试了webpack2第3个项目reload,服务启动不了,O(∩_∩)O谢谢
image

riskers

riskers commented on Apr 5, 2017

@riskers
OwnerAuthor

@547377507 不要在中文目录下吧

13 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FEweb 前端

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @riskers@xvinc@gengxuelei@jl1014171068@lijialiang

        Issue actions

          教你一步步从零构建webpack开发多页面环境 · Issue #27 · riskers/blog