Vue+Webpack打造todo应用 原

一、课程介绍

课程地址:https://www.imooc.com/learn/935

1.概要

2.vue-loader+webpack项目配置

安装vs-code和插件

EditorConfig for VS Code
ESLint
gitignore
language-stylus
Nunjucks
One Dark Pro
PostCSS syntax
Vetur
View In Browser
vscode-icons

软件配置

{
    "window.zoomLevel": 2,
    "editor.fontSize": 20,
    "workbench.iconTheme": "vscode-icons",
    "files.autoSave": "onFocusChange",
    "terminal.integrated.fontSize": 20,
    "editor.tabSize": 2
}

打开命令行:ctrl+~

初始化项目

npm init

安装

npm i webpack@^3.10.0 vue vue-loader@^13.6.0

注意:

A.webpack需要安装到4版本以下

B.vue-loader需要安装15版本以下(参考官方文档 https://vue-loader.vuejs.org/migrating.html#a-plugin-is-now-required . Vue-loader在15.*之后的版本都是 vue-loader的使用都是需要伴生 VueLoaderPlugin的)

安装警告依赖

npm i css-loader vue-template-compiler

初始化好了

新建src/app.vue

<template>
    <div id="test">{{text}}</div>
</template>
<script>
export default {
    data(){
        return{
            text:'abc'
        }
    }
}
</script>
<style>
#test{color: red}
</style>

添加脚本

index.js

import Vue from 'vue'
import App from './app.vue'

const root = document.createElement('div')

document.body.appendChild(root)

new Vue({
  render:(h) => h(App)
}).$mount(root)

 webpack.config.js

"build": "webpack --config webpack.config.js"

执行打包命令 

npm run build

生成dist文件夹

红色报错,说明没有编译器解释

npm run build

3. webpack配置项目加载各种静态资源及css预处理器

npm i style-loader url-loader file-loader

url-loader是建立在file-loader基础上的,base64,。

limit对文件大小做限制。

use不仅是读取,还包括做的一些处理。

[name]原文件名,[ext]扩展名

如运行还有其他报错,可参考上图给安装依赖版本

npm run build

新建styles/test-stylus.styl样式文件,写法很随意,可以不要大括号和冒号等

npm run build

需要安装stylus-loader

npm i stylus-loader stylus

4.webpack-dev-server的配置使用

安装

npm i webpack-dev-server@^2.9.7

这个版本应该与webpack版本相互兼容,3.1.5版本会报错,推测要在3以下

针对不同平台的依赖

npm i cross-env

说明:mac上不需要用set,windows上需要

配置好dev

引入html的插件

安装

npm i html-webpack-plugin

配置

使用插件,在js中可以直接引用环境判断,vue可以根据不同环境打包,开发环境会有很多错误提示,但是正式环境不需要

npm run dev

打开查看

设置热加载

package.js(部分)

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
    "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js"
  },

webpack.config.js

const path = require('path')

const HTMLPlugin = require('html-webpack-plugin')
const webpack = require('webpack')

const isDev = process.env.NODE_ENV === 'development'

const config = {
  target: 'web',
  entry:path.join(__dirname,'src/index.js'),
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.styl/,
        use: [
          'style-loader',
          'css-loader',
          'stylus-loader'
        ]
      },

      {
        test: /\.(gif|jpg|jepg|png|svg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 1024,
              name: '[name]-aaa.[ext]'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: isDev ? '"development"' : '"production"'
      }
    }),
    new HTMLPlugin()
  ]
}

if (isDev) {
  config.devtool = '#cheap-module-eval-source-map' // 浏览器打开后,通过映射以编译后我们能看懂方式调整,source-map最完整映射关系,但是编译效率比较低,文件比较大,eval可能看起来会比较乱,出现行对应不齐的问题。而推荐的这个效率比较高
  config.devServer = {
    port: 8000,
    host: '0.0.0.0',
    overlay: {
      errors: true,
    },
    hot: true // 改了一个组件的代码,只重新渲染这个组件,不贵整个页面渲染
    // historyFallback: {

    // }// 入口地址映射,(略)
    // open: true //启动后自动打开页面
  },
  config.plugins.push(
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin() // 不需要信息展示的问题
  )
}

module.exports = config

二、vue2介绍和项目实践

1.vue2的核心知识介绍

2.配置vue的jsx写法以及postcss

安装插件

npm i postcss-loader autoprefixer babel-loader babel-core

项目文件夹下创建babelrc和postcss.config.js配置文件

postcss是在css文件都编译完之后,通过autoprefixer对其优化,需要加入浏览器前缀支持的

将vue-jsx文件的转化

npm i babel-preset-env babel-plugin-transform-vue-jsx

安装依赖

编译处理

更快一些

npm i babel-helper-vue-jsx-merge-props@^2.0.0 css-loader@^0.28.7 babel-plugin-syntax-jsx
npm run dev

打包没报错即可

3. 实现todo应用的界面

调整assets文件夹到src下

删除之前测试使用的

新建src/todo及其以下文件

新建assets/styles/globel.styl文件

引入

样式内容略,删除app.vue的内容

样式具体内容略。lang指定预处理器,scoped仅仅对本组件作用

(1)基本组件的引用,样式

header.vue

<template>
  <header class="main-header">
    <h1>Todo</h1>
  </header>
</template>
<style lang = 'stylus' scoped>
.main-header{
  text-align center
  h1{
    font-size 100px
    color rgba(175,47,47,0.6)
    font-weight 500
    margin 20px
  }
}
</style>

todo.vue

<template>
  <section class="real-app">
    <input 
      type="text"
      class="add-input"
      autofocus='autofocus'
      placeholder="接下来要做什么"
      @keyup.enter="addTodo"
    >
    <item :todo='todo'></item>
    <!-- @keyup 也就等于 v-on:keyup -->
    <tabs :filter = 'filter'></tabs>
  </section>
</template>
<script>
import Item from './item.vue'
import Tabs from './tabs.vue'
export default {
  data() {
    return{
      todo:{
        id:0,
        content:'this is todo',
        completed: false
      },
      filter:'all'
    }
  },
  methods: {
    addTodo(){

    }
  },
  components: {
    Item,Tabs
  }
}
</script>
<style lang=stylus scoped>
.real-app{
  width 70%
  min-width 500px
  padding 10px
  background #ffffff
  color #555555
  margin 20px auto
  box-shadow 0 0 10px 5px rgba(0,0,0,0.5)
  .add-input{
    width 84%
    font-size 24px
    border none
    margin 0 6%
    padding 20px 2%
    outline none
    border-bottom 1px solid #fff
  }
  .add-input:hover{
    color #333
    border-bottom 1px solid #ccc
    cursor pointer
  }
}
</style>

tabs.vue

<template>
  <div class="helper">
    <span class="left">2 items left</span>
    <span class="tabs">
      <span
        v-for="state in states"
        :key="state"
        :class="[state, filter === state ? 'actived' : '']"
        @click="toggleFilter(state)"
      >
        {{state}}
      </span>
    </span>
    <span class="clear" @click="clearAllCompleted" type='button'>
      Clear Completed
    </span>
  </div>
</template>

<script>
export default {
  props: {
    filter: {
      type: String,
      required: true
    }
  },
  data(){
    return{
      states:['all', 'active','completed']
    }
  },
  methods: {
    clearAllCompleted (){},
    toggleFilter(){}
  }
}
</script>
<style lang="stylus" scoped>
.helper{
  text-align center
  .left{width:18%;display inline-block}
  .tabs{
    width 50%
    display inline-block
    margin 10px auto
    span{
      min-width 33%
      display inline-block 
      }
    span:hover{
      cursor pointer
      color orange
    }
    span.actived{
      color red
      }
    }
  .clear{ 
    width 30%
    display inline-block 
    color:grey
    border none
    outline none
    }
  .clear:hover{
    color black
    cursor pointer
    }
  input[type='button']{
    border-width 0
  }
}
</style>

item.vue

<template>
  <div :class="['todo-item',todo.completed ? 'completed' : '']">
    <input 
      type="checkbox"
      class="toggle"
      v-model="todo.completed"
    >
    <label>{{todo.content}}</label>
    <button class="destory" @click="deleteTodo"></button>
  </div>
</template>
<script>
export default {
  props:{
    todo:{
      type:Object,
      required:true
    }
  },
  methods:{
    deleteTodo(){

    }
  }
}
</script>

<style lang=stylus scoped>
.todo-item{
  vertical-align text-top
  border none
  padding 15px 2%
  width 88%
  margin 10px 4%
  border-bottom 1px solid #eee
  font-size 28px
  input[type="checkbox"],input[type="checkbox"]:checked{
    vertical-align text-top
    width 30px
    height 30px
  }
  .destory{
    width 30px
    height 30px
    float  right
  }
}
</style>

footer.jsx

import '../assets/styles/footer.styl'
export default {
  data() {
    return {
      author: 'Jhon'
    }
  },
  render(){
    return (
      <div id="footer">
        <span>written by {this.author}</span>
      </div>
    )
  }
}

footer.styl

#footer
  background:rgba(0,0,0,0.2)
  text-align: center
  margin-top: 50px

global.styl

body
  background-image: url('../images/bg.jpg')

4.实现todo应用的业务逻辑

(1)添加和删除操作

父子组件通信

(2)数量统计,tab切换面板,clear清楚所选item

至此代码:

tabs.vue

<template>
  <div class="helper">
    <span class="left">{{unFinishedTodoLength}} items left</span>
    <span class="tabs">
      <span
        v-for="state in states"
        :key="state"
        :class="[state, filter === state ? 'actived' : '']"
        @click="toggleFilter(state)"
      >
        {{state}}
      </span>
    </span>
    <span class="clear" @click="clearAllCompleted">
      Clear Completed
    </span>
  </div>
</template>

<script>
export default {
  props: {
    filter: {
      type: String,
      required: true
    },
    todos: {
      type: Array,
      required: true
    }
  },
  data(){
    return{
      states:['all', 'active','completed']
    }
  },
  methods: {
    clearAllCompleted (){
      this.$emit('clearAllcompleted')
    },
    toggleFilter(state){
      this.$emit('toggle', state)
    }
  },
  computed: {
    unFinishedTodoLength(){
      return this.todos.filter(todo => !todo.completed).length
    }
  }
}
</script>
<style lang="stylus" scoped>
.helper{
  text-align center
  .left{width:18%;display inline-block}
  .tabs{
    width 50%
    display inline-block
    margin 10px auto
    span{
      min-width 33%
      display inline-block 
      }
    span:hover{
      cursor pointer
      color orange
    }
    span.actived{
      color red
      }
    }
  .clear{ 
    width 30%
    display inline-block 
    color:grey
    border none
    outline none
    }
  .clear:hover{
    color black
    cursor pointer
    }
  input[type='button']{
    border-width 0
  }
}
</style>

todo.vue

<template>
  <section class="real-app">
    <input 
      type="text"
      class="add-input"
      autofocus='autofocus'
      placeholder="接下来要做什么"
      @keyup.enter="addTodo"
    >
    <item 
      :todo='todo'
      v-for="todo in filterdTodos"
      :key="todo.id"
      @del="deleteTodo"
    />
    <!-- @keyup 也就等于 v-on:keyup -->
    <tabs 
      :filter = 'filter' 
      :todos="todos"
      @toggle='toggleFilter'
      @clearAllcompleted="clearAllcompleted"
    
    />
  </section>
</template>
<script>
import Item from './item.vue'
import Tabs from './tabs.vue'
let id = 0
export default {
  data() {
    return{
      todos:[],
      filter:'all'
    }
  },
  methods: {
    addTodo(e){
      this.todos.unshift({
        id: id++,
        content: e.target.value.trim(),
        completed: false
      })
      e.target.value =''
    },
    deleteTodo(id){
      this.todos.splice(this.todos.findIndex(todo => todo.id === id), 1)
    },
    toggleFilter(state){
      this.filter = state
    },
    clearAllcompleted(){
      this.todos = this.todos.filter(todo => !todo.completed)
    }
  },
  computed: {
    filterdTodos(){
      if (this.filter === 'all'){
        return this.todos
      }
      // 判断
      const completed = this.filter === 'completed'
      return this.todos.filter(todo => completed === todo.completed)
    }
  },
  components: {
    Item,Tabs
  }
}
</script>
<style lang=stylus scoped>
.real-app{
  width 70%
  min-width 500px
  padding 10px
  background #ffffff
  color #555555
  margin 20px auto
  box-shadow 0 0 10px 5px rgba(0,0,0,0.5)
  .add-input{
    width 84%
    font-size 24px
    border none
    margin 0 6%
    padding 20px 2%
    outline none
    border-bottom 1px solid #fff
  }
  .add-input:hover{
    color #333
    border-bottom 1px solid #ccc
    cursor pointer
  }
}
</style>

item.vue 

<template>
  <div :class="['todo-item',todo.completed ? 'completed' : '']">
    <input 
      type="checkbox"
      class="toggle"
      v-model="todo.completed"
    >
    <label>{{todo.content}}</label>
    <button class="destory" @click="deleteTodo"></button>
  </div>
</template>
<script>
export default {
  props:{
    todo:{
      type:Object,
      required:true
    }
  },
  methods:{
    deleteTodo(){
      this.$emit('del', this.todo.id)
    }
  }
}
</script>

<style lang=stylus scoped>
.todo-item{
  vertical-align text-top
  border none
  padding 15px 2%
  width 88%
  margin 10px 4%
  border-bottom 1px solid #eee
  font-size 28px
  input[type="checkbox"],input[type="checkbox"]:checked{
    vertical-align text-top
    width 30px
    height 30px
  }
  .destory{
    width 30px
    height 30px
    float  right
  }
}
</style>

三、webpack配置优化

1. 配置css单独分离打包

npm i extract-text-webpack-plugin
const path = require('path')

const HTMLPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
const ExtractPlugin = require('extract-text-webpack-plugin')
const isDev = process.env.NODE_ENV === 'development'

const config = {
  target: 'web',
  entry:path.join(__dirname,'src/index.js'),
  output: {
    filename: 'bundle[hash:8].js',
    path: path.join(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.jsx/,
        loader: 'babel-loader'
      },
      {
        test: /\.(gif|jpg|jepg|png|svg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 1024,
              name: '[name]-aaa.[ext]'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: isDev ? '"development"' : '"production"'
      }
    }),
    new HTMLPlugin()
  ]
}

if (isDev) {
  config.module.rules.push({
      test: /\.styl/,
      use: [
        'style-loader',
        'css-loader',
        {
          loader: 'postcss-loader',
          options: {
            sourceMap: true,
          }
        },
        'stylus-loader'
      ]
  })
  config.devtool = '#cheap-module-eval-source-map' // 浏览器打开后,通过映射以编译后我们能看懂方式调整,source-map最完整映射关系,但是编译效率比较低,文件比较大,eval可能看起来会比较乱,出现行对应不齐的问题。而推荐的这个效率比较高
  config.devServer = {
    port: 8000,
    host: '0.0.0.0',
    overlay: {
      errors: true,
    },
    hot: true // 改了一个组件的代码,只重新渲染这个组件,不贵整个页面渲染
    // historyFallback: {

    // }// 入口地址映射,(略)
    // open: true //启动后自动打开页面
  },
  config.plugins.push(
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin() // 不需要信息展示的问题
  )
} else{
  config.output.filename = '[name][chunkhash:8].js'
  config.module.rules.push(
    {
      test: /\.styl/,
      use: ExtractPlugin.extract({
        fallback: 'style-loader',
        use: [
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              sourceMap: true
            }
          },
          'stylus-loader'
        ]
      })
    }  
  ),
  config.plugins.push(
    new ExtractPlugin('styles.[contentHash:8].css')
  )
}

module.exports = config
npm run build

打包生成文件名

附带:package.json

{
  "name": "vue-ssr-tech",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
    "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "autoprefixer": "^7.2.3",
    "babel-core": "^6.26.3",
    "babel-helper-vue-jsx-merge-props": "^2.0.0",
    "babel-loader": "^7.1.2",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-vue-jsx": "^3.7.0",
    "babel-preset-env": "^1.7.0",
    "cross-env": "^5.1.3",
    "css-loader": "^0.28.7",
    "extract-text-webpack-plugin": "^3.0.2",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.2.0",
    "postcss-loader": "^3.0.0",
    "style-loader": "^0.22.1",
    "stylus": "^0.54.5",
    "stylus-loader": "^3.0.2",
    "url-loader": "^1.0.1",
    "vue": "^2.5.17",
    "vue-loader": "^13.7.2",
    "vue-template-compiler": "^2.5.17",
    "webpack": "^3.10.0",
    "webpack-dev-server": "^2.9.7"
  },
  "devDependencies": {}
}

如有版本报错,可根据上面替换安装

2.区分打包类库代码及hash优化

让浏览器加载更快

npm run build

hash和chunkhash区别:

chunkhash是每个模块一个hash,hash会有区别,而hash是整个应用一个hash

webpack.config.js

const path = require('path')

const HTMLPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
const ExtractPlugin = require('extract-text-webpack-plugin')
const isDev = process.env.NODE_ENV === 'development'

const config = {
  target: 'web',
  entry:path.join(__dirname,'src/index.js'),
  output: {
    filename: 'bundle[hash:8].js',
    path: path.join(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.jsx/,
        loader: 'babel-loader'
      },
      {
        test: /\.(gif|jpg|jepg|png|svg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 1024,
              name: '[name]-aaa.[ext]'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: isDev ? '"development"' : '"production"'
      }
    }),
    new HTMLPlugin()
  ]
}

if (isDev) {
  config.module.rules.push({
      test: /\.styl/,
      use: [
        'style-loader',
        'css-loader',
        {
          loader: 'postcss-loader',
          options: {
            sourceMap: true,
          }
        },
        'stylus-loader'
      ]
  })
  config.devtool = '#cheap-module-eval-source-map' // 浏览器打开后,通过映射以编译后我们能看懂方式调整,source-map最完整映射关系,但是编译效率比较低,文件比较大,eval可能看起来会比较乱,出现行对应不齐的问题。而推荐的这个效率比较高
  config.devServer = {
    port: 8000,
    host: '0.0.0.0',
    overlay: {
      errors: true,
    },
    hot: true // 改了一个组件的代码,只重新渲染这个组件,不贵整个页面渲染
    // historyFallback: {

    // }// 入口地址映射,(略)
    // open: true //启动后自动打开页面
  },
  config.plugins.push(
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin() // 不需要信息展示的问题
  )
} else{
  config.entry = {
    app: path.join(__dirname,'src/index.js'),
    vendor: ['vue']
  }
  // config.output.filename = '[name].[hash:8].js'
  config.output.filename = '[name].[chunkhash:8].js'
  config.module.rules.push(
    {
      test: /\.styl/,
      use: ExtractPlugin.extract({
        fallback: 'style-loader',
        use: [
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              sourceMap: true
            }
          },
          'stylus-loader'
        ]
      })
    }  
  ),
  config.plugins.push(
    new ExtractPlugin('styles.[contentHash:8].css'),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'verdor'
    }),
    // 在有新的模块加入的时候,webpack是会给新模块加入id的,插入顺序不同,倒是id会变化,使用浏览器的缓存就是去效果,这种方式可以规避。verdor要放在runtime前面
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime'
    })
  )
}

module.exports = config

四、总结

搭建项目-webpack构建(eg:vue-loader,jsx->babel,静态资源,缓存)

webpack中文网:https://www.webpackjs.com/

webapck官网(要翻墙):http://webpack.github.io/

配置很多

vue开发todo应用,.vue文件,数据传递,拆分组建,双向绑定,虚拟DOM,jsx文件-复杂场景(vue2)

错误解答

1.No parser and no filepath given

npm install vue-loader@^13.7.2

2.You may need an appropriate loader to handle this file type

解决:可能有些包的版本不合适,需要更改

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏岑志军的专栏

(7)theos安装

22040
来自专栏Youngxj

Html5 Canvas多表盘时钟绘制

22740
来自专栏闰土大叔

入职第三天:vue-loader在项目中是如何配置的

这是我入职第三天的故事,在写这篇文章之前,先来看看咱们今天要讲的主角——vue-loader,你对它了解多少?

12810
来自专栏Python攻城狮

web服务器1.HTTP协议介绍2.http协议分析3.总结4Web静态服务器

HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写。它的发展是万维网协会(World Wide Web Consorti...

10410
来自专栏IT可乐

HTML中的超链接

超链接:也叫URL(Uniform Resource Locator),就是统一资源定位器。一般效果是我们点击网页上某个地方,网页会自动跳转到另外一个地方。 一...

41950
来自专栏前端人人

React多页面应用7(webpack4 生产环境配置)

本教程总共9篇,每日更新一篇,请关注我们!你可以进入历史消息查看以往文章,也敬请期待我们的新文章! 1、React多页面应用1(webpack4 开发环境搭建,...

53680
来自专栏程序员同行者

zabbix告警“Zabbix poller processes more than 75% busy”

31530
来自专栏野路子程序员

Laravel 视图中AJAX请求、jquery-ujs异步使用DELETE请求时配置X-CSRF-TOKEN

36160
来自专栏Nian糕的私人厨房

WebPack 模块化打包工具(下)

本篇博文的内容根据 入门 Webpack,看这篇就够了 该篇文章总结而来,其代码、模块示例、功能拓展部分均有所删减,若是想了解更多关于 WebPack 的详细内...

47250
来自专栏coding

2018年python3与selenium教程第4节前进和后退操作cookie操作选项卡异常处理

20830

扫码关注云+社区

领取腾讯云代金券