《晋级篇:SPA单页面应用(组件化开发+SASS+ES6)》

目标

建立一个lesson4项目,基于sass编写css、基于ES6语法规则编写js代码实现组件化开发。

挑战

在原项目基础上把html语言改成vue或react,进行组件化开发。

知识点

1、sass:用sass代替编写css;
2、ES6语法:ES6新增了很多有趣的特性;
3、externals:打包时忽略第三方库,比如说jquery;
4、resolve属性:改变模块的处理方式;
5、UglifyJsPlugin:webpack自带插件,可以对打包js文件进行压缩或美化处理;
6、BannerPlugin:给打包文件头部加上你的签名;
7、open-browser-webpack-plugin:自动打开浏览器插件;

课程内容

新建一个lesson4文件,做初始化

  1. mkdir lesson4 && cd lesson4
  2. npm init -y
  3. npm install webpack webpack-dev-server css-loader extract-text-webpack-plugin file-loader html-loader html-webpack-plugin style-loader url-loader --save-dev
  4. touch webpack.config.js webpack.entry.js
  5. mkdir src && cd src
  6. touch index.html
  7. mkdir components && cd components
  8. mkdir header body footer
  9. touch header/header.html header/header.scss header/header.js body/body.html body/body.scss body/body.js footer/footer.html footer/footer.scss footer/footer.js

初始化完之后对应开发文件目录结构如下:
《晋级篇:SPA单页面应用(组件化开发+SASS+ES6)》 - 图1
copy以下代码到index.html

  1. <html>
  2. <head>
  3. <meta charset="UTF-8" />
  4. <title>webpack-lesson4</title>
  5. </head>
  6. <body>
  7. <header>
  8. <!-- html-loader:用于引入对应资源 -->
  9. ${require('./components/header/header.html')}
  10. </header>
  11. <section>
  12. ${require('./components/body/body.html')}
  13. </section>
  14. <footer>
  15. ${require('./components/footer/footer.html')}
  16. </footer>
  17. <!-- 跟webpack.config.js中的externals属性结合,作用包括:1、打包时会忽略jquery文件;2、可全局调用jquery -->
  18. <script type="text/javascript" src="//cdn.bootcss.com/jquery/3.2.0/jquery.min.js"></script>
  19. </body>
  20. </html>

copy以下代码到header.html

  1. <h1 class="header-title">this is header</h1>

copy以下代码到body.html

  1. <h1 class="body-title">this is body</h1>
  2. <ul class="body-list">
  3. <li class="body-list-item" id="body-input">你可以使用BannerPlugin给你的每个打包文件加上你的签名<br>webpack教程<br>by kingvid</li>
  4. </ul>

copy以下代码到footer.html

  1. <h1 class="footer-title">this is footer</h1>

copy以下代码到header.scss

  1. .header-title{
  2. font-style: italic;
  3. background-color: rgba(100,100,100,0.9);
  4. color: #fff;
  5. }

copy以下代码到body.scss

  1. .body-title{
  2. border-radius: 4px;
  3. border: solid 1px #ccc;
  4. color: #fff;
  5. background-color: rgba(0,0,0,0.9);
  6. }
  7. .body-list{
  8. margin: auto;
  9. list-style: none;
  10. .body-list-item{
  11. font-size: 20px;
  12. }
  13. }

copy以下代码到footer.scss

  1. .footer-title{
  2. color: #fff;
  3. background-color: rgba(200,200,200,0.9);
  4. }

copy以下代码到body.js

  1. // 这里不再需要再import或require jquery,在webpack.config.js中新增了externals属性,让jquery可以在webpack整个运行环境中被调用
  2. let element = $("#body-input"),
  3. str = element.html(),
  4. progress = 0,
  5. timer = setInterval(() => {
  6. let current = str.substr(progress, 1);
  7. if (current == '<') {
  8. progress = str.indexOf('>', progress) + 1;
  9. } else {
  10. progress++;
  11. }
  12. element.html(str.substring(0, progress) + (progress && 1 ? '_': ''));
  13. if (progress >= str.length) {
  14. clearInterval(timer);
  15. element.html(str.substring(0, progress));
  16. }
  17. },150);

配置package.json命令行

  1. "scripts": {
  2. "start": "export NODE_ENV=development && node_modules/.bin/webpack-dev-server",
  3. "build": "export NODE_ENV=production && node_modules/.bin/webpack"
  4. }

sass-loader用来解析scss文件,它的安装依赖于node-sass

  1. npm install sass-loader node-sass --save-dev

babel-loader可以将ES6语法转化成能被浏览器识别的ES5,开发js我们就能使用最新的ES6语法了

  1. npm install --save-dev babel-loader babel-core babel-preset-env

copy以下代码到webpack.config.js

  1. var path = require('path'),
  2. HtmlWebpackPlugin = require('html-webpack-plugin'),
  3. webpack = require('webpack'),
  4. ExtractTextPlugin = require("extract-text-webpack-plugin"),
  5. OpenBrowserPlugin = require('open-browser-webpack-plugin');
  6. module.exports = {
  7. entry: process.env.NODE_ENV === 'production' ? './webpack.entry': ['webpack-dev-server/client?http://localhost:8080', 'webpack/hot/only-dev-server', './webpack.entry.js'],
  8. output: {
  9. filename: 'webpack.bundle.js',
  10. path: path.resolve(__dirname, './build'),
  11. publicPath: ''
  12. },
  13. context: __dirname,
  14. module: {
  15. rules: [{
  16. test: /\.scss$/,
  17. // 解析scss文件
  18. use: process.env.NODE_ENV === 'production' ? ExtractTextPlugin.extract({
  19. fallback: "style-loader",
  20. use: ["css-loader", "sass-loader"]
  21. }) : ['style-loader', 'css-loader?sourceMap', 'sass-loader?sourceMap']
  22. },
  23. {
  24. test: /\.(jpg|png)$/,
  25. use: ['url-loader?limit=10000&name=img/[name].[ext]']
  26. },
  27. {
  28. test: /\.html$/,
  29. use: {
  30. loader: 'html-loader',
  31. options: {
  32. interpolate: 'require'
  33. }
  34. }
  35. },
  36. {
  37. test: /\.js$/,
  38. exclude: /node_modules/,
  39. use: {
  40. loader: 'babel-loader',
  41. options: {
  42. presets: ['env']
  43. }
  44. }
  45. }]
  46. },
  47. plugins: process.env.NODE_ENV === 'production' ? [
  48. new HtmlWebpackPlugin({
  49. template: './src/index.html',
  50. filename: 'index.html'
  51. }),
  52. new ExtractTextPlugin("style.css"),
  53. new webpack.DefinePlugin({
  54. 'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
  55. }),
  56. // 压缩js文件
  57. new webpack.optimize.UglifyJsPlugin({
  58. compress: {
  59. warnings: true
  60. }
  61. }),
  62. // 给打包文件加上你的签名
  63. new webpack.BannerPlugin({
  64. banner: 'This is created by kingvid'
  65. })
  66. ] : [
  67. new HtmlWebpackPlugin({
  68. template: './src/index.html',
  69. filename: 'index.html'
  70. }),
  71. new webpack.HotModuleReplacementPlugin(),
  72. new webpack.NamedModulesPlugin(),
  73. new webpack.DefinePlugin({
  74. 'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
  75. }),
  76. new OpenBrowserPlugin({
  77. url: 'http://localhost:8080/'
  78. }) // 自动在浏览器中打开 http://localhost:8080/
  79. ],
  80. devServer: {
  81. contentBase: path.resolve(__dirname, 'src'),
  82. hot: true,
  83. noInfo: false
  84. },
  85. devtool: 'source-map',
  86. // 打包时将不会把以下第三方库打包进webpack.bundle.js中但可被webpack全局调用,比如说jquery,但需要在html文件中用script引入jquery
  87. externals: {
  88. jquery: 'jQuery'
  89. },
  90. // 改变模块的处理方式
  91. resolve: {
  92. extensions: ['.js', '.scss', '.html'],
  93. // eg:入口文件改成webpack.entry,打包时webpack会先检索webpack.entry文件,返回结果为空时给文件补上.js文件尾缀再继续检索,依此类推。
  94. alias: {
  95. // 这里可以给一些常用的模块添加别名,可以减少webpack查找该模块的时间,比如说:vue
  96. // 'vue': 'vue/dist/vue.common.js'
  97. }
  98. }
  99. };

修改的地方主要有:
1、修改loader:sass和babel;
2、在plugins新增UglifyJsPlugin插件,打包时会对js文件做压缩;
3、OpenBrowserPlugin:运行npm start命令会自动打开浏览器窗口;
4、新增resolve属性:给文件自动添加尾缀、减少webpack查找常用模块的时间;
5、新增externals属性:引入且不打包第三方库;
6、在plugins新增BannerPlugin插件,运行npm run build,打开webpack.bundle.js可看到
《晋级篇:SPA单页面应用(组件化开发+SASS+ES6)》 - 图2

copy以下代码到webpack.entry.js

  1. const cssAndJsContext = require.context('./src', true, /\.(js|scss)$/i);
  2. console.log(cssAndJsContext.keys());
  3. // 结果是:["./base.scss","./components/body/body.js","./components/body/body.scss","./components/footer/footer.js","./components/footer/footer.scss","./components/header/header.js","./components/header/header.scss"]
  4. // cssAndJsContext('./base.scss') 相当于 require("./src/base.scss");
  5. cssAndJsContext.keys().forEach((key) => {
  6. cssAndJsContext(key);
  7. });
  8. if (NODE_ENV === 'development') {
  9. const htmlContext = require.context('./src', true, /\.html$/i);
  10. htmlContext.keys().forEach((key) => {
  11. htmlContext(key);
  12. });
  13. }

require.context的作用是可以把在自己设置的目录下所有符合条件的文件一次性require到webpack运行环境中,它有三个参数:

  1. require.context(directory, useSubdirectories = false, regExp = /^\.\//)
  2. # 它会返回一个webpackContext的函数结果,通过调用返回对象的.keys()方法可以获取检索结果
  3. # directory:设定在哪个目录下检索文件,必须是相对路径
  4. # useSubdirectories:是否在当前目录下的所有子目录进行检索,而不限制只在directory当前目录下检索
  5. # regExp:正则表达式,即检索条件,webpack.entry.js检索条件是所有文件名以.js或.scss结尾的文件,且不区分大小写

运行npm run build,本地打开index.html,效果如下:
《晋级篇:SPA单页面应用(组件化开发+SASS+ES6)》 - 图3

补充

上面提到的require.context()会把在主目录下所有符合条件的文件路径都返回出来,如果要直接在require中使用变量表达式动态引入模块的话,要注意引入格式,先看个例子:

  1. var name = './src/base.scss';
  2. require(name);

在webpack.entry.js中写入以上代码的话,解析会报错且require不到base.scss,如果这样写

  1. var name = 'base';
  2. require('./src/'+name+'.scss');

就能成功require到base.scss文件,这是因为webpack在解析require函数调用时会抽取两个信息:

  1. Directory(指定目录): ./src
  2. Regular expression(检索条件): /^.*\.scss$/

只有成功拿到这两个信息之后,webpack才会在指定目录下寻找符合条件的文件。

总结

对于第三方库的引入,上面的externals需要在html中手动引入jquery,可能有些人会觉得麻烦,其实还有另外一种解决方案,这里就要用到webpack强大的也更为复杂的code splitting,即代码分割。见下一节lesson5